tl;dr: Dado um tipo numérico T, existe uma maneira concisa de declarar uma variável como std::uniform_int_distribution<T>
ou std::uniform_real_distribution<T>
dependendo se T é integral ou de ponto flutuante?
Eu precisava produzir std::chrono::duration
s aleatórios uniformemente distribuídos em um intervalo definido pelo chamador, então criei um uniform_duration_distribution
modelo de classe modelado com base nos modelos de classe de distribuição da biblioteca padrão.
Primeiro, escrevi um conceito para restringir minha distribuição a uma duração cronológica (ou tipo similar).
// Is T similar enough to std::chrono::duration for our purposes?
template <typename T>
concept chrono_duration = requires (T d) {
{ d.count() } -> std::same_as<typename T::rep>;
{ d.zero() } -> std::same_as<T>;
{ d.max() } -> std::same_as<T>;
};
Uma duração tem uma representação numérica chamada contagem. Minha classe contém uma distribuição uniforme numérica da biblioteca padrão, usa-a para gerar uma contagem e constrói uma duração a partir dessa contagem.
template<chrono_duration DurationT>
class uniform_duration_distribution {
// ...
private:
using rep = typename DurationT::rep;
std::uniform_distribution<rep> m_distribution; // Whoops!
};
E aí está o problema. O tipo da contagem da duração pode ser um tipo integral ou um tipo de ponto flutuante, então o tipo de m_distribution
não é tão simples std::uniform_distribution<T>
porque não existe tal modelo.
Eu não queria fazer várias especializações da minha classe, e não queria limitar os chamadores a uma instanciação específica de uma duração. Eu só queria escolher o tipo para a distribuição contida com base no tipo de rep da duração.
Minha primeira tentativa foi usar um modelo de alias de tipo restrito por conceitos.
template <std::integral IntT>
using dist_selector = std::uniform_int_distribution<IntT>;
template <std::floating_point FloatT>
using dist_selector = std::uniform_real_distribution<FloatT>;
Isso não parece ser permitido. Eu posso (aparentemente) restringir um único modelo de alias using com um conceito, mas não posso usar conceitos para selecionar entre diferentes aliases. Pelo menos, não como eu tentei. Existe uma maneira de fazer isso?
Também aprendi que não posso me especializar usando modelos de alias.
No final, criei um modelo de estrutura com especializações para os tipos numéricos.
// Select the appropriate distribution type based on the value type.
template <typename T> struct dist_selector {};
template <> struct dist_selector<long double> { using t = std::uniform_real_distribution<long double>; };
template <> struct dist_selector<double> { using t = std::uniform_real_distribution<double>; };
template <> struct dist_selector<float> { using t = std::uniform_real_distribution<float>; };
template <> struct dist_selector<long long> { using t = std::uniform_int_distribution<long long>; };
template <> struct dist_selector<long> { using t = std::uniform_int_distribution<long>; };
template <> struct dist_selector<int> { using t = std::uniform_int_distribution<int>; };
template <> struct dist_selector<short> { using t = std::uniform_int_distribution<short>; };
template <> struct dist_selector<unsigned long long> { using t = std::uniform_int_distribution<unsigned long long>; };
template <> struct dist_selector<unsigned long> { using t = std::uniform_int_distribution<unsigned long>; };
// ...
Então a variável membro é definida como:
using rep = typename DurationT::rep;
using dist_type = typename dist_selector<rep>::t;
dist_type m_distribution;
Isso funciona, mas parece que estou voltando para um hack antigo. Estou esquecendo de uma maneira mais moderna de fazer isso?
Eu começaria com uma característica/conceito de tipo para restringir o parâmetro do modelo a ser de um
std::chrono::duration
tipo:Então, um seletor de distribuição poderia se parecer com isto, onde uma instância de
T
deve corresponder a uma dastest
funções restritas:Montando tudo:
Demonstração
Você pode usar um modelo de classe para a especialização por meio dos conceitos e, em seguida, adicionar o modelo de alias para maior conveniência:
Demonstração ao vivo
Alternativamente, você pode usar
std::conditional
:Demonstração ao vivo
Observe como
std::type_identity
evita solicitar o::type
alias de membro que não existe (por exemplo,std::type_identity<std::uniform_int_distribution<double>>
é do tipo "ok", mas não temtype
alias de membro).