Выберите распределение случайных чисел во время компиляции
Я пишу тесты, используя 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>
являются частью решения, но я пока не смог ничего скомпилировать. Я в основном пробовал две вещи:
- Сделайте своего рода время компиляции, используя специализацию шаблона.
- использование
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
распределение.