coincoin
coincoin

Reputation: 4685

Call appropriate constructor depending on value_type : integer or float

I have a function which fills a container with random values between min and max using uniform distribution.

#include <iostream>
#include <random>
#include <algorithm>
#include <vector>

template<typename TContainer>
void uniform_random(TContainer& container, 
const typename TContainer::value_type min, 
const typename TContainer::value_type max) {
    std::random_device rd;
    std::mt19937 gen(rd());

    // Below line does not work with integers container
    std::uniform_real_distribution<typename TContainer::value_type> distribution(min, max);

    auto lambda_norm_dist = [&](){ return distribution(gen); };
    std::generate(container.begin(), container.end(), lambda_norm_dist);
}

int main() {
    std::vector<float> a(10);
    uniform_random(a,0,10);
    for (auto el : a) { std::cout << el << " "; }
}

Replacing std::vector<float> with std::vector<int> does not work since I would have to use std::uniform_int_distribution instead. Is there a simple and elegant way to pick the right constructor depending on the value_type parameter ?

I was trying so far to use std::numeric_limits<typename TContainer::value_type>::is_integer without success.

Upvotes: 5

Views: 529

Answers (4)

Cassio Neri
Cassio Neri

Reputation: 20533

In C++14 (or C++11 with a minor change) you can create a uniform_distribution type alias in this way:

template <typename ValueType>
using uniform_distribution = std::conditional_t<
    std::is_floating_point<ValueType>::value,
    std::uniform_real_distribution<ValueType>,
    std::uniform_int_distribution<ValueType>
 >;

Usage:

uniform_distribution<typename TContainer::value_type> distribution(min, max);

Upvotes: 7

eerorika
eerorika

Reputation: 238401

One solution is to use a type trait helper and std::enable_if:

template<class T, class Enable = void>
struct uniform_distribution_helper {};

template<class T>
struct uniform_distribution_helper<T, typename std::enable_if<std::is_floating_point<T>::value >::type> {
    using type = std::uniform_real_distribution<T>;
};

template<class T>
struct uniform_distribution_helper<T, typename  std::enable_if<std::is_integral<T>::value >::type> {
    using type = std::uniform_int_distribution<T>;
};

Then in your function:

using uniform_distribution = typename uniform_distribution_helper<typename TContainer::value_type>::type;
// Below line does not work with integers container
uniform_distribution distribution(min, max);

Upvotes: 4

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361612

Write a meta-function select_distribution which allows you to write this:

using value_type = typename TContainer::value_type;

using distribution_type = typename select_distribution<value_type>::type;

distribution_type  distribution(min, max);

where select_distribution is defined as:

template<typename T, bool = std::is_floating_point<T>::value>   
struct select_distribution
{
  using type = std::uniform_real_distribution<T>;
};

template<typename T>   
struct select_distribution<T, false>
{
  using type = std::uniform_int_distribution<T>;
};

Hope that helps.

Upvotes: 6

Asaf
Asaf

Reputation: 4407

Try using a traits class. For example:

template <typename NumType>
struct ValueTraits;

template <>
struct ValueTraits<int>
{        
    using UniformDistributionType = std::uniform_int_distribution<int>;
};

You can do it based on types (and then you might need quite a few Traits classes), or based on a bool value (the result of is_integer), and then you'll need to template your using.

And then in your class:

// In the class scope:
typedef TContainer::value_type value_type;

// And in your constructor:
typename ValueTraits<value_type>::UniformDistributionType distribution(min, max);

Upvotes: 0

Related Questions