简体   繁体   中英

Partial specialization and the need for std::void_t<>

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 {};

(ie 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.)

This is not a consequence of SFINAE or partial specialization ordering, but due to the use of default template parameters. Informally, the reason is that the application of default template parameters happens before the search for template definitions, including possible specializations.

So in the above case, the code that says is_default_constructable<int> is actually requesting to instantiate a template is_default_constructable<int, void> after applying the default second parameter. Then possible definitions are considered.

The "primary" template definition clearly matches and is included. The given partial specialization

template <typename T> struct is_default_constructable<T, decltype(T()) > : std::true_type {};

is actually defining is_default_constructable<int, int> which does not match the requested is_default_constructable<int, void> so the specialization is ignored, even if the substitution succeeds. This leaves the primary definition (inheriting false_type) as the only viable definition so it is chosen.

When the specialization has wrap<> (or std::void_t<> ) to force the second arg to a void , the specialization is defining is_default_constructable<int, void> which matches the request. This definition (asuming the substitution succeeds, ie T() is well formed) is more specialized than the primary definition (according to super-complicated rules for ordering specializations), so it is chosen.

As an aside, the above naive implementations probably don't work as expected when T is a reference type or other corner cases, which is a good reason to use the standard library versions of all this. Them standards committee people are way smarter than I am and have already thought of all these things.

This answer and this answer to somewhat-related questions have more detailed information that set me right.

And, yes, I can't spell constructible, assuming that's even a word. Which is another good reason to use the standard library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM