It might help to look at how has_type
is actually used by the detector:
template<template<class ...> class Op, typename ...Args>
struct Detector<void_t< Op<Args...>>, Op, Args...> {
// ^^ ^^
// has_type Yes/No
static constexpr bool value = true;
};
For this specialization to match the compiler must make sure that Op<Args...>
, when replacing the parameters (Op
and Args...
) with the actual arguments (has_type
and Yes
/No
), must name a type (since that's what the template void_t
requires as first template argument).
Since has_type
is not a type, but rather an alias of some type, it must look whether whatever is aliased names a type.
For Yes
this will be Yes::type
, which again is an alias of void
. void
is a type, so everything is fine, the specialization matches, value
is true
.
For No
this will be No::type
, which does not exist (No
has no member type
after all). Thus, the substitution fails (but this is not an error, SFINAE), the specialization cannot be used. Thus the compiler chooses the base template, where value
is false
.
Now what happens when you define has_type
as follows:
template<typename T>
struct has_type { using type = typename T::type; }
Then above specialization needs (in the No
case) that a type has_type<No>
exists. has_type
is a class template, which given some type (No
is a type, so everything good) "produces" a type. Thus, has_type<No>
is a type. Thus the specialization matches, value
is true
.
The members of has_type<No>
are not needed at this point. You could even use template<typename> struct has_type;
(only a declaration, no definition). In other words, it may be an incomplete type:
A template argument for a type template parameter must be a type-id, which may name an incomplete type [..]
http://en.cppreference.com/w/cpp/language/template_parameters
The contents only matter when the compiler actually needs them, e.g. for creating an object of that type:
// Class template with some random members.
template<typename T>
struct Foo {
using baz = typename T::baz;
constexpr static int value = T::value * 42;
};
// Class template which is even only declared
template<typename X> struct Bar; // no definition
// Does not use its template parameter in any way. Needs just a type name.
template<typename> struct Defer {};
int main() {
Defer<Foo<int>> ok;
Defer<Bar<int>> ok_too;
// Foo<int> fail;
// Bar<int> fail_too;
return 0;
}
This mechanism is often used for "type tags", which e.g. can be used to create different types with identical "content" from a single template:
template<typename /* TAG */, typename ValueType>
struct value_of_strong_type {
ValueType value;
// ...
};
struct A_tag; // no definition
using A = value_of_strong_type<A_tag, int>;
struct B_tag; // no definition
using B = value_of_strong_type<B_tag, int>;
Both A
and B
behave identically, but are not convertible to each other, because they're completely different types.
To make the detector work with such class templates as you showed you need the following specialization:
template<template<class ...> class Op, typename ...Args>
struct Detector<void_t<typename Op<Args...>::type>, Op, Args...> {
// ^^^^^^^^ ^^^^^^
static constexpr bool value = true;
};
Though you cannot just add it, otherwise you run into ambiguous resolution errors.