Question

I want to make a traits for std::uniform_*_distribution according to type given. E.g.:

distribution_traits<float>::type int_dist;

I tried following ways, but none of them compiles, and I don't know why.

Implementation 1

Use std::enable_if with typedefs:

template <typename T>
struct distribution_traits {
  using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
  using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
};

Clang 3.4 complains:

dist_traits.cpp:7:9: error: redefinition of 'type'
  using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
        ^
dist_traits.cpp:6:9: note: previous definition is here
  using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
        ^
dist_traits.cpp:6:40: error: no type named 'type' in 'std::enable_if<false, std::uniform_int_distribution<float> >'; 'enable_if' cannot be used to
      disable this declaration
  using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~
dist_traits.cpp:28:3: note: in instantiation of template class 'distribution_traits<float>' requested here
  distribution_traits<float>::type int_dist;
  ^
2 errors generated.

Implementation 2

Use enable_if as class template parameter:

template <typename T, typename distribution_t = void>
struct distribution_traits;

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

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

And Clang complains

dist_traits.cpp:28:3: error: implicit instantiation of undefined template 'distribution_traits<float, void>'
  distribution_traits<float>::type int_dist;
  ^

Either way cannot be compiled by MSVC++ 12.0, and the error messages are similar.

Could anyone please explain what's wrong I'm doing with SFINAE? Thanks!


For those who are curious about solution, here is the one that compiles:

template <typename T>
auto dist() -> typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;

template <typename T>
auto dist() -> typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;

template <typename T>
struct distribution_traits {
  using type = decltype(dist<T>());
};

BTW, if put dist function into distribution_traits, the compilation will fail with error: function only differs in return type cannot be overloaded. :(

Was it helpful?

Solution

SFINAE can be used to discard overloads of function templates and class template specializations during substitution of template arguments.

It cannot be used with type/template aliases like you're trying to do.

About your working code - putting dist inside the class doesn't work because you attempt to call dist inside decltype without an object. Make dist static and it'll work:

template <typename T>
struct distribution_traits {
  template <typename U>
  static auto dist() -> typename std::enable_if<std::is_integral<U>::value, std::uniform_int_distribution<U>>::type;

  template <typename U>
  static auto dist() -> typename std::enable_if<std::is_floating_point<U>::value, std::uniform_real_distribution<U>>::type;    

  using type = decltype(dist<T>());
};

For implementation 2 to work, you need to omit the second argument of enable_if:

template struct distribution_traits;

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

otherwise the specialization you define is distribution_traits<T, uniform_int_distribution<T>> and that doesn't match an instantiation like distribution_traits<float> because the second parameter is defaulted to void.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top