Detection Idiom and SFINAE via Inheritance

Viewed 78

I can use std::experimental::is_detected to check whether chips1_t can be instantiated with float* (which it can't) as shown in the static_assert below:

#include <experimental/type_traits>
#include <type_traits>

template <typename, typename = void>
  struct chips1;

template <typename T>
struct chips1<T*,std::enable_if_t<std::is_same<T,int>::value>> {
  using type = int;
};

template <typename T> using chips1_t = typename chips1<T>::type;

static_assert(!std::experimental::is_detected<chips1_t,float*>::value,"");

If I then try a check using chips2_t, a similar static_assert, shown below, will produce a compilation error; regarding the missing type member. Can anyone tell me why?

struct Base {};

template <typename>
  struct chips2;

template <typename T>
struct chips2<T*> : std::enable_if_t<std::is_same<T,int>::value,Base> {
  using type = int;
};

template <typename T> using chips2_t = typename chips2<T>::type;

static_assert(!std::experimental::is_detected<chips2_t,float*>::value,"");
1 Answers

In the second case, the base class is not part of the template type, so it's not considered during SFINAE (It's a dependent base class)

Instantiating

template <typename T>
struct chips2<T*>

Succeeds, and then compilation fails because it derives from std::enable_if_t<std::is_same<T,int>::value,Base> which becomes an ill-formed expression.


You cannot specialize templates by their base class*.

For example, you couldn't have two competing specializations like:

template <typename T>
struct chips2<T*> : std::enable_if_t<std::is_same<T,int>::value,Base> {
  using type = int;
};

template <typename T>
struct chips2<T*> : std::enable_if_t<!std::is_same<T,int>::value,Base> {
  using type = float;
};

They would be considered identical from a specialization standpoint (i.e., they are identical from a naming standpoint, so the compiler will consider the second one to be a redeclaration of the first one). The only part that matters is everything up to the colon :

In the first case, your enable_if is directly part of the template specialization, so SFINAE works properly.


Another note

In the second one, you've effectively made it impossible to instantiate chips2 with anything other than int*, see the following example:

struct Base {};

template <typename>
struct chips2
{};

template <typename T>
struct chips2<T*> 
   : std::enable_if_t<std::is_same<T,int>::value, Base> 
{};

int main(){
    chips2<float*> c;
}

You might be inclined to think that SFINAE will choose the base class template for c, but in reality it chooses the specialization chips2<T*> because of what I said above, and compilation fails.


*Relevant standardese at [temp.spec]

the name of the class that is explicitly specialized shall be a simple-template-id.

Where a simple-template-id is of the form:
   template-name < template-argument-listopt >

e.g. chips2<T*>. Note that there is no option to also include the class it derives from

Related