3

I have some experience with std::enable_if. IIRC, it is about if a well-formed expression results in true return back the user type T (if given) or void via nested type alias.

template<bool,typename = void>
struct enable_if;

template<typename T>
struct enable_if<true,T>{
 using type = T;
};

template <typename T, typename = void>
struct base_template{
     enum { value= false};
};

template <typename T>
struct base_template<T, typename enable_if<std::is_integral<T>::value>::type> {
    enum { value= true};// something useful...
};

struct some{};
static_assert(base_template<some>::value,"F*"); //error: static assertion failed: F
static_assert(base_template<int>::value,"F*");

But in boost.Hana I see this trait when<> and its implementation is like

template <bool condition>
struct when;

template <typename T, typename = when<true>>
struct base_template{
     enum { value= false};
};

template <typename T>
struct base_template<T, when<std::is_integral<T>::value>> {
    enum { value= true};// something useful...
};

struct some{};

static_assert(base_template<int>::value,"F*");
static_assert(base_template<some>::value,"F*");<source>:28:15: error: static assertion failed: F*

How the SFINAE works here? though the std::is_integral<some>::value is going to result in false, it doesn't mean(it does?) that the when<false> is ill-formed and should dispatch the instantiation to the primary class template. Am I missing anything here?

RaGa__M
  • 2,550
  • 1
  • 23
  • 44
  • 1
    @ildjarn surely they aren't identical because `enable_if_t ` is a type alias yet `when` is a class template and unlike `enable_if_t ` I don't see `when` is pulling any nested type off either. – RaGa__M Jun 09 '18 at 15:56

1 Answers1

2

It's the same general idea. You can use enable_if_t or decltype in basically the same way. Now, you're probably used to seeing SFINAE partial specializations like this:

template<class T, class U = void>
struct Foo {};

template<class T>
struct Foo<T, decltype(T::bar())> {};

... Foo<X> ...

Here, Foo<X> is first expanded by the compiler to Foo<X, void> (because you didn't provide U at the "call site", so the default U = void is filled in instead). Then, the compiler looks for the best-matching specialization of class template Foo. If decltype(X::bar()) is in fact void, then Foo<T, decltype(T::bar())> [with T=X] will be a perfect match for Foo<X, void>. Otherwise, the generic Foo<T, U> [with T=X, U=void] will be used instead.

Now for the Hana when example.

template<bool> struct when {};

template<class T, class U = when<true>>
struct Foo {};

template<class T>
struct Foo<T, when<T::baz>> {};

... Foo<X> ...

Here, Foo<X> is first expanded by the compiler to Foo<X, when<true>> (because you didn't provide U at the "call site", so the default U = when<true> is filled in instead). Then, the compiler looks for the best-matching specialization of class template Foo. If when<X::baz> is in fact when<true>, then Foo<T, when<T::baz>> [with T=X] will be a perfect match for Foo<X, when<true>>. Otherwise, the generic Foo<T, U> [with T=X, U=when<true>] will be used instead.

You can replace the simple expression T::baz in my example with any arbitrarily complicated boolean expression, as long as it's constexpr-evaluable. In your original example, the expression was std::is_integral<T>::value.

My CppCon 2017 session "A Soupçon of SFINAE" walks through some similar examples.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • So is it a special rule for the specialization's second type to match up with the default argument or it uses the primary template? I get what is happening, but I don't grasp the "why". – Jason Rice Jun 10 '18 at 01:57
  • The primary template is the "least specialized" version; the partial specialization is "more specialized." All else being equal, the compiler will choose to go with the most specialized (least generic) version possible, which in this case means choosing the partial specialization `Foo [with T=X]` over the less specialized `Foo [with T=X, U=void]`. If we had written a *full* specialization `template<> struct Foo {}`, then the compiler would have picked `Foo` over the less specialized `Foo [with T=X]`. That's why template specialization works. – Quuxplusone Jun 10 '18 at 02:03