Выбор унифицированного типа дистрибутива из стандартной библиотеки

tl;dr: Учитывая числовой тип T, существует ли краткий способ объявить переменную как std::uniform_int_distribution<T> или std::uniform_real_distribution<T> в зависимости от того, является ли T целым или с плавающей запятой?


Мне нужно было создать случайные std::chrono::duration, равномерно распределенные по диапазону, определяемому вызывающей стороной, поэтому я создал шаблон uniform_duration_distribution класса, смоделированный по образцу шаблонов классов распределения стандартной библиотеки.

Сначала я написал концепцию, позволяющую ограничить мое распределение длительностью хрона (или аналогичным типом).

// 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>;
};

Длительность имеет числовое представление, называемое счетчиком. Мой класс содержит числовое равномерное распределение из стандартной библиотеки, использует его для генерации счетчика и строит продолжительность на основе этого счетчика.

template<chrono_duration DurationT>
class uniform_duration_distribution {
  // ...
  private:
    using rep = typename DurationT::rep;
    std::uniform_distribution<rep> m_distribution;  // Whoops!
};

И в этом проблема. Тип счетчика длительности может быть либо целочисленным, либо типом с плавающей запятой, поэтому тип m_distribution не такой простой, как std::uniform_distribution<T>, поскольку такого шаблона не существует.

Я не хотел делать несколько специализаций своего класса и не хотел ограничивать вызывающих абонентов одним конкретным экземпляром duraiton. Я просто хотел выбрать тип автономного распределения на основе типа повторения продолжительности.

Моей первой попыткой было использовать шаблон псевдонима типа, ограниченный концепциями.

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>;

Кажется, это запрещено. Я могу (по-видимому) ограничить один шаблон использования псевдонима концепцией, но я не могу использовать концепции для выбора между различными псевдонимами. По крайней мере, не так, как я пробовал. Есть ли способ сделать это?

Я также узнал, что не могу специализироваться на использовании шаблонов псевдонимов.

В конце я создал шаблон структуры со специализацией для числовых типов.

// 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>; };
// ...

Тогда переменная-член определяется как:

using rep = typename DurationT::rep;
using dist_type = typename dist_selector<rep>::t;
dist_type m_distribution;

Это работает, но похоже на возврат к старому хаку. Мне не хватает более современного способа сделать это?

Как вы собираетесь инициализировать дистрибутив? Предполагая, что это достаточно небольшой диапазон [a,b], вам нужно будет инициализировать Uniform_int_distribution с помощью a,b и Uniform_real_distribution с помощью a,nextafter(b,max). А затем при генерации для Uniform_real_distribution вам придется повторно генерировать, чтобы отбросить >b, чтобы обойти распространенную ошибку. Так что дело не только в типе. Вероятно, он заслуживает своего собственного типа распространения или, по крайней мере, типа признаков.

Jeff Garrett 29.08.2024 23:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать шаблон класса для специализации с помощью концепций, а затем для удобства добавить шаблон псевдонима:

#include <random>
#include <concepts>
#include <type_traits>

template <typename T> struct dist_selector;

template <typename T> requires std::integral<T>
struct dist_selector<T> {
     using type = std::uniform_int_distribution<T>; 
};

template <typename T> requires std::floating_point<T>
struct dist_selector<T> { 
    using type = std::uniform_real_distribution<T>; 
};

template <typename T>
using dist_selector_t = dist_selector<T>::type;


int main () {
    static_assert(std::is_same_v<std::uniform_int_distribution<int>,dist_selector_t<int>>);
    static_assert(std::is_same_v<std::uniform_real_distribution<float>,dist_selector_t<float>>);
}

Живая демо

Альтернативно вы можете использовать std::conditional:

template <typename T>
using dis_sel = std::conditional_t<std::is_floating_point_v<T>,
                            std::type_identity<std::uniform_real_distribution<T>>,
                            std::type_identity<std::uniform_int_distribution<T>>>::type;

Живая демо

Обратите внимание, как std::type_identity избегает запроса псевдонима участника ::type, который не существует (например, std::type_identity<std::uniform_int_distribution<double>> — это тип «ОК», у него просто нет псевдонима участника type).

Ах, гибрид того, что я пробовал. Я этого не видел. Спасибо! Я еще не изучал std::type_identity, но тоже посмотрю.

Adrian McCarthy 29.08.2024 23:29

Я бы начал с признака/концепции типа, чтобы ограничить параметр шаблона типом std::chrono::duration:

template<class>
struct is_duration : std::false_type {};

template<class Rep, class Period>
struct is_duration<std::chrono::duration<Rep, Period>> : std::true_type {};

template<class T>
concept chrono_duration = is_duration<T>::value;

Тогда селектор распределения мог бы выглядеть так, где экземпляр T должен соответствовать одной из ограниченных функций test:

template<class T>
struct distribution_selector {
    template<class I>
        requires std::integral<I>
    static auto test(I) -> std::uniform_int_distribution<I>;

    template<class F>
        requires std::floating_point <F>
    static auto test(F) -> std::uniform_real_distribution<F>;

    using type = decltype(test(std::declval<T>()));
};

template<class T>
using distribution_selector_t = distribution_selector<T>::type;

Собираем это вместе:

template<chrono_duration DurationT>
class uniform_duration_distribution {
public:
    using rep = DurationT::rep;

    distribution_selector_t<rep> m_distribution;
};

Демо

Да, это работает, и ты научил меня вещам. Однако объявлять тестовые функции для получения типа возвращаемого значения кажется несколько окольным путем. Хотя это немного короче, я не уверен, что читателю легче понять, что происходит, по сравнению с утомительным списком специализаций, который у меня был.

Adrian McCarthy 29.08.2024 23:38

Другие вопросы по теме

Аргументы функции шаблона с переменным числом аргументов не принимаются
Специализация шаблона на C++ с использованием Enable_if
Специализация шаблона C++ STD внутри другого пространства имен
Почему определение оператора = (а не объявление) должно быть написано, когда подходящий шаблон легко доступен
Использование SFINAE в конструкторе, чтобы проверить, существует ли конструктор типа члена
Использование if-constexpr и концепций для обнаружения экземпляра определенного типа расширенной политики
Почему квалификатор const игнорируется при применении к выведенной ссылке Lvalue в C++?
Создание декартова произведения на основе аргумента шаблона целочисленного диапазона
Почему выведение std::call_once не удалось и возникла ошибка «не удалось вывести параметр шаблона ‘_Callable’»
Невозможно получить доступ к специализации класса шаблона через универсальную ссылку