3

I am writing tests using the TYPED_TEST feature of google tests, which allows me to generalize a test to multiple types. I am testing a class template for the types int and double. In a test, I would need to generate random numbers. To do so, I have tried using the std::uniform_int_distribution<T> and the std::uniform_real_distribution<T> but have ran into static asserts.

As the names indicate, std::uniform_int_distribution<T> checks if T is an integral type and std::uniform_real_distribution<T> checks that T is a floating point type.

Since my test automatically tests for int and then for double, I have been trying to write some kind of function that would allow me to chose the right kind of distribution for the type at compile time. More precisely, something like:

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

Note that this is only a pseudocode of what I have been trying to do. This kind of logical branch fails because the if AND the else have to compile.

I have done some research on how to do this and I feel like std::is_integral<T> and std::is_floating_point<T> are part of the solution, but I have not been able to compile anything so far. I mainly tried two things:

  1. Make a kind of compilation time by using template specialization.
  2. Use enable_if.

Using the first approach I ended up with an error message telling me my overloads were ambiguous. Using the second approach, I tried some stuff but got lost in the abominable syntax (at least for someone not used to it) which it lead to.

Do you have a suggestion on how this could be accomplished?

P.S. I would like to see how this could be done, so splitting my test in two would not be an acceptable answer for me.

2 Answers 2

7

C++17

I you may use C++17, you can make use of 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

For C++11 and C++14, you could use a conditional extra template type parameter in your template parameter list to select the return type as well as the distribution.

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 (return type deduced by auto and using the std::conditional_t helper type short form for 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); } 
Sign up to request clarification or add additional context in comments.

1 Comment

Just tested the C++11 answer (it was the one I needed) and it worked exactly as advertised. C++17 better describes what I had in mind, good to know.
2

I sometimes use std::conditional like this:

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

If you pass the function integer parameters it selects the int distribution otherwise it selects the real distribution.

3 Comments

I believe that typename in the return is not necessary. Furthermore, if you use c++14, then you can get rid off the other one as well, by using std::conditional_t (::type is unnecessary then as well). And if you are not using c++14 then condional_t is really easy to write on your own, so might be worth considering.
@paler123 Unfortunately typename is necessary in the return because param_type is a template parameter dependant type.
Agreed, sry could've checked.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.