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

Я пишу тесты, используя TYPED_TEST особенность гугл тестов, которая позволяет обобщать тест на несколько типов. Я тестирую шаблон класса для типов int а также double, В тесте мне нужно было бы генерировать случайные числа. Для этого я попытался использовать std::uniform_int_distribution<T> и std::uniform_real_distribution<T> но наткнулся на статические утверждения.

Как видно из названия, std::uniform_int_distribution<T> проверяет, если T является интегральным типом и std::uniform_real_distribution<T> проверяет, что T тип с плавающей запятой

Поскольку мой тест автоматически проверяет int а затем для double Я пытался написать какую-то функцию, которая позволила бы мне выбрать правильный тип распределения для типа во время компиляции. Точнее, что-то вроде:

template<class T>
Distribution get_right_distribution(const T& a, const T& b)
{
    if(T is integral) // Compile time is needed, runtime 
                      // fails since both if and else have to compile
    {
        return std::uniform_real_distribution(a, b);
    }
    else
    {
        return std::uniform_real_distribution(a, b);
    }
}

Обратите внимание, что это всего лишь псевдокод того, что я пытался сделать. Этот вид логической ветви терпит неудачу, потому что if И else должен скомпилировать.

Я провел некоторое исследование о том, как это сделать, и я чувствую, что std::is_integral<T> а также std::is_floating_point<T> являются частью решения, но я пока не смог ничего скомпилировать. Я в основном пробовал две вещи:

  1. Сделайте своего рода время компиляции, используя специализацию шаблона.
  2. использование enable_if,

Используя первый подход, я получил сообщение об ошибке, в котором говорилось, что мои перегрузки были неоднозначными. Используя второй подход, я попробовал кое-что, но заблудился в отвратительном синтаксисе (по крайней мере, для того, кто к нему не привык), к которому это привело.

У вас есть предложение о том, как это можно сделать?

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

2 ответа

Решение

C++17

Если вы можете использовать C++17, вы можете использовать if constexpr(...):

#include <iostream>
#include <random>
#include <type_traits>

template <typename T>
auto get_right_distribution(const T a, const T b) {
    if constexpr(std::is_integral<T>::value) {
        return std::uniform_int_distribution(a, b);
    }
    else {
        return std::uniform_real_distribution(a, b);
    }
}

int main() {
    std::random_device rd;
    std::mt19937 gen(rd());

    auto int_dis = get_right_distribution(1, 6);
    std::cout << int_dis(gen) << "\n";

    auto float_dis = get_right_distribution(1.F, 6.F);
    std::cout << float_dis(gen) << "\n";
}

C++ 11 и C++14

Для C++ 11 и C++14 вы можете использовать условный дополнительный параметр типа шаблона в списке параметров шаблона, чтобы выбрать тип возвращаемого значения, а также распределение.

C++ 11:

template <typename T,
          typename Distribution = typename std::conditional<
              std::is_integral<T>::value, 
              std::uniform_int_distribution<T>,
              std::uniform_real_distribution<T>>::type>
Distribution get_right_distribution(const T a, const T b) {
    return Distribution(a, b);
}

C++14 (возвращаемый тип, выведенный auto и используя std::conditional_t короткая форма типа помощник для std::conditional<...>::type):

template <typename T,
          typename Distribution = typename std::conditional_t<
              std::is_integral<T>::value, 
              std::uniform_int_distribution<T>,
              std::uniform_real_distribution<T>>>
auto get_right_distribution(const T a, const T b) {
    return Distribution(a, b);
}

Я иногда использую std::conditional вот так:

template<typename Number>
Number random_number(Number from, Number to)
{
    static_assert(std::is_integral<Number>::value
               || std::is_floating_point<Number>::value,
                   "parameters must be integer or floating point types");

    using Distribution = typename std::conditional
    <
        std::is_integral<Number>::value,
        std::uniform_int_distribution<Number>,
        std::uniform_real_distribution<Number>
    >::type;

    // in reality I usually get the generator from another
    // function, but for many purposes this is fine.
    thread_local static std::mt19937 mt{std::random_device{}()};
    thread_local static Distribution dist;

    return dist(mt, typename Distribution::param_type{from, to});
}

Если вы передаете целочисленные параметры функции, она выбирает int распределение в противном случае он выбирает real распределение.

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