简体   繁体   中英

Choose random number distribution at compile time

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?

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM