Use enable_if with is_integral to make distribution traits - c++

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. :(

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.

Related

alias template is not a class template?

I am trying to write a type trait to detect if a type has a T::type of certain type. I am using code from this answer. For reference this is the part of the code I am using:
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
I started simple with a trait to detect the T::type :
template <typename T>
using has_type_t = typename T::type;
template <typename T>
using has_type = detect<T, has_type_t>;
This works as expected, but when I ask for the actual type of T::type also I get errors that I do not understand:
template <typename X>
struct has_X_type_helper {
template <typename T>
using type = typename std::enable_if_t<std::is_same_v< typename T::type, X>,int>;
};
template <typename T,typename X>
using has_X_type = detect<T,has_X_type_helper<X>::type>;
GCC:
<source>:49:55: error: type/value mismatch at argument 2 in template parameter list for 'template<class, template<class> class<template-parameter-1-2>, class> struct detect'
49 | using has_X_type = detect<T,has_X_type_helper<X>::type>;
| ^
<source>:49:55: note: expected a class template, got 'has_X_type_helper<X>::type'
and Clang is even more confusing for me
<source>:49:29: error: template argument for template template parameter must be a class template or type alias template
using has_X_type = detect<T,has_X_type_helper<X>::type>;
^
Is has_X_type_helper<X>::type not a type alias template ? What is wrong in my code?
# godbolt
You need to indicate that the nested thing is a template:
template <typename T, typename X>
using has_X_type = detect<T, has_X_type_helper<X>::template type>;
// ~~~~~~~~~^

SFINAE on Error in Dependent Type causes unexpected hard error

I have code that I can simplify down to something like this:
#include <type_traits>
template <typename T>
struct dependent
{
using type = typename T::type;
};
template <typename T>
typename dependent<T>::type
foo(const T& x);
bool foo(bool x) { return x; }
int main()
{
foo(true);
}
This fails to compile with g++ 9.3 with --std=c++17 with the error:
test.cpp: In instantiation of 'struct dependent<bool>':
test.cpp:11:1: required by substitution of 'template<class T> typename dependent<T>::type foo(const T&) [with T = bool]'
test.cpp:17:13: required from here
test.cpp:6:11: error: 'bool' is not a class, struct, or union type
6 | using type = typename T::type;
| ^~~~
This is not what I would expect. I would expect that attempting to substitute bool for T in template <typename T> typename dependent<T>::type foo(const T& x) would be a failure, which is not an error. It seems SFINAE is not working for me, but I do not know why.
From the examples in the unofficial reference on SFINAE:
Substitution proceeds in lexical order and stops when a failure is encountered.
template <typename A>
struct B { using type = typename A::type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename B<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo (int);
I am hitting the case on class U = typename B<T>::type, but the "guaranteed to not occur as of C++14" bit seems to indicate that this should not be happening as of C++14. What gives?
Issue is that dependent<T> has type, but that one might be ill formed causing hard failure.
You might make dependent SFINAE friendly:
template <typename T, typename Enabler = void>
struct dependent
{
};
template <typename T>
struct dependent<T, std::void_t<typename T::type>>
{
using type = typename T::type;
};
Demo

Templates: SFINAE specialization not getting applied

The below snippet is, at least I thought, as straightward an example as possible of SFINAE applied to specialization.
The last line is the main point, and where the failure is occurring. The definition of a specialized foo template determines the specialization of the bar template, or so I want. Other bar specializations can be defined elsewhere, or perhaps the use of arbitary types can simply remain unsupported.
The same pattern is widely suggested for use with enable_if, as I understand.
template <typename T>
struct foo;
template <>
struct foo<int> {
using type = int;
};
template <typename T, typename use = void>
struct bar;
template <typename T>
struct bar<T, typename foo<T>::type> {
using type = typename foo<T>::type;
};
using good = typename foo<int>::type;
using bad = typename bar<int>::type;
In g++, with 14 or 17 standard, the result is shown below. It seems that the bar specialization is not getting applied, and the compiler is using the unspecialized (empty) definition. Why?
$ g++ --std=c++14 special.cpp -o special
special.cpp:18:32: error: ‘type’ in ‘struct bar<int>’ does not name a type
using bad = typename bar<int>::type;
template <typename T>
struct bar<T, typename foo<T>::type> {
using type = typename foo<T>::type;
};
should be
template <typename T>
struct bar<T, std::void_t<typename foo<T>::type>> {
using type = typename foo<T>::type;
};
as use should always be void.
That's why naming as AlwaysVoid would be better.
(or use its role as something like Enabler)
template <typename T, typename AlwaysVoid = void>
struct bar;

Alias template for reference type passed as template template argument in SFINAE context

I encountered the following problem with G++ 6.1.0 (-std=c++14 switch) and I don't understand why the compiler rejects the code.
I have a helper struct is_well_formed which checks if a supplied template template argument is well formed when substituting another supplied type into it:
template<template<typename> typename R, typename T, typename = void>
struct is_well_formed : std::false_type {};
template<template<typename> typename R, typename T>
struct is_well_formed<R, T, void_t<R<T>>> : std::true_type {};
I want to check whether a type is referenceable. So my idea was to write the following:
// (#1)
template<class T>
using reference_t = T&;
static_assert(!is_well_formed<reference_t, void>::value, "Reference to void!?");
But I get a compiler error:
main.cpp: In instantiation of 'struct is_well_formed<reference_t, double>':
main.cpp:62:51: required from here
main.cpp:54:20: error: type/value mismatch at argument 1 in template parameter list for 'template<template<class> class R, class T, class> struct is_well_formed'
: std::false_type {};
^
main.cpp:54:20: note: expected a class template, got 'reference_t'
main.cpp:54:20: error: type/value mismatch at argument 1 in template parameter list for 'is_well_formed<R, T, <template-parameter-1-3> >::is_well_formed'
main.cpp:54:20: note: expected a class template, got 'reference_t'
Alternatively the following works with the same static_assert:
// (#2)
template<class T>
using reference_t = void_t<T&>;
Furthermore the following works, which really puzzles me:
// (#3)
template<class T>
using pointer_t = T*;
static_assert(is_well_formed<pointer_t, void>::value, "No pointer to void!?");
What is the difference between the three aliases? Is the void_t<T&> solution the most elegant? Or is it possible to modify the is_well_formed helper struct to support the first reference version?
EDIT: I tested the code with MSVC"15" Preview 4, and (#1) and (#3) work including the asserts. But when I try (#2) the assert for the void reference does not work, i.e. information gets lost during substitution and the false_type overload is never selected. Which compiler is right?
The is_well_formed helper corresponds to the can_apply struct from the which was once documented on Stack Overflow documentation page on SFINAE, I just removed the parameter packs. Full example code:
#include <utility>
// Only defined in std for C++17
template <class...>
using void_t = void;
// (#1) Compiler error during substitution in is_well_formed
template<class T>
using reference_t = T&;
// (#2) Ok, asserts work
/*
template<class T>
using reference_t = void_t<T&>;
*/
// (#3) Ok, asserts work
template<class T>
using pointer_t = T*;
template<template<typename> typename R, typename T, typename = void>
struct is_well_formed
: std::false_type {};
template<template<typename> typename R, typename T>
struct is_well_formed<R, T, void_t<R<T>>>
: std::true_type {};
int main(int, char**)
{
static_assert(is_well_formed<reference_t, double>::value, "No reference to double!?");
static_assert(!is_well_formed<reference_t, void>::value, "Reference to void!?");
static_assert(is_well_formed<pointer_t, double>::value, "No pointer to double!?");
static_assert(is_well_formed<pointer_t, void>::value, "No pointer to void!?");
return 0;
}
It may be a compiler bug and user T.C. reported it on the GCC Bugzilla here after seeing this post. User Jarod42 proposed
template<class T>
using reference = decltype(std::declval<T&>());
as a working alternative for both MSVC15 and GCC 6.1.0. Furthermore he noted that MSVC still requires the long void_t definition
template <class...>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type;
instead of the obvious
template <class...>
using void_t = void;
which prevented option (#2) in the original post from working.

Variadic alias template to non-variadic class template

In trying to write a simple example for currying of metafunction classes, I wrote the following:
#include <type_traits>
struct first {
template <typename T, typename U>
using apply = T;
};
template <typename C, typename... Args>
struct curry {
struct type {
template <typename... OtherArgs>
using apply = typename C::template apply<Args..., OtherArgs...>;
};
};
int main() {
static_assert(std::is_same<first::apply<int, char>, int>::value, ""); // OK
using AlwaysInt = curry<first, int>::type;
static_assert(std::is_same<AlwaysInt::apply<char>, int>::value, ""); // error
}
The second static_assert fails to compile on both gcc 5.1:
main.cpp:17:72: error: pack expansion argument for non-pack parameter 'U' of alias template 'template<class T, class U> using apply = T'
using apply = typename C::template apply<Args..., OtherArgs...>;
^
and clang 3.6:
main.cpp:17:59: error: pack expansion used as argument for non-pack parameter of alias template
using apply = typename C::template apply<Args..., OtherArgs...>;
^~~~~~~~~~~~
Same error in both cases. However, if I outsource the application in curry to a separate metafunction:
template <typename C, typename... Args>
struct eval {
using type = typename C::template apply<Args...>;
};
template <typename C, typename... Args>
struct curry {
struct type {
template <typename... OtherArgs>
using apply = typename eval<C, Args..., OtherArgs...>::type;
};
};
Both compilers compile just fine. Is there something wrong with the original example or is this just a bug in both compilers?