One for the language lawyers....
I'm playing around with SFINAE and TMP, trying to get a deeper understanding.
Consider the following code, a naive implementation of std::is_default_constructible
#include <type_traits>
template <typename T, typename = void> struct is_default_constructable : std::false_type {};
template <typename T> struct is_default_constructable<T, decltype(T()) > : std::true_type {};
class NC { NC(int); }; // Not default constructable
#include <iostream>
int main(int, char **)
{
std::cout << "int is_default_constructible? " << is_default_constructable<int>() << std::endl;
std::cout << "NC is_default_constructible? " << is_default_constructable<NC>() << std::endl;
}
This compiles fine, but doesn't actually work, it returns false for all types.
For the NC case, this is as I'd expect, T()
is not well-formed so that specialization is discarded due to SFINAE and the primary template (false_type) is used. But for the int
case, I'd expect the specialization to be used as decltype(T())
is valid and equivalent to T
.
If, based on the actual code in <type_traits>
, I change the specialization to
template <typename T> using wrap = void;
template <typename T> struct is_default_constructable<T, wrap<decltype(T())> > : std::true_type {};
(i.e. wrap the second template parameter in a mockup of std::void_t<>
which forces the second type to be void
), this works as expected.
Even more curious, variations of this scheme using types other than void
as the default type in the primary template or wrap<>
also fail, unless the two types are the same.
Can someone explain why the type of wrap<>
and the second template argument default type need to be the same in order for the specialization to be selected?
(I'm using "g++ -Wall --std=c++17" with g++ version 6.3, but I think this is not compiler-related.)