简体   繁体   中英

SFINAE: Understanding void_t and detect_if

I'm learning about template metaprogramming, and recently, I saw a talk on the CPPConference about void_t. Soon after that, I found out about the detection idiom .

However, I still have a hard time understanding either one of them (especially the detection idiom, since it's based on void_t). I read this blog post, and this stackoverflow post, which helped me a bit, but I still have some questions.

If my understanding is right, if an expression inside void_t is invalid, it will be SFINAEd out using this expression:

template< class, class = std::void_t<> >
struct has_type_member : std::false_type { };

Because class is here a default template parameter that can represent any number of parameters independent of their type? Is it even necessary to say that class equals to std::void_t<> ? Wouldn't it be enough to write

template< class, class = void >
struct has_type_member : std::false_type { };

And if not, why?

However, if the expression is valid, this expression will be called an evaluated to void:

template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };

Why would a valid expression be evaluated to void, and how does this help us? Also, why does the expression need to be valid to match void_t?

Ok, I don't claim that I habe understood all perfectly, myself, but I will try to answer to the best of my knowledge:

Because class is here a default template parameter that can represent any number of parameters independent of their type?

Nearly. This template will match any instantiation with one or two template arguments, so all of the form has_type_member<T> or has_type_member<T, U> . This is due to

  • class matches any type. But this not special (you could also write class T you just don't need the name, cause you don't reference it in the declaration) Every template at first matches all types and could only be differentiated by the number of arguments. Just often, we have either a constraint through some SFINAE-magic (like enable_if ) or we have a better fit by partial template specification later on.
  • class = void matches every type, as above and also no type at all, since void fills in if we have no argument.

We will only instantiate this template as has_member_type<T> , so this will always be the first match, but maybe not the best match. But being the first match, it tells us: The second template argument has to be void , since all further matches must either be a partial specification. Otherwise we would get ambiguity. Think, what would happen if the second template would give us int if the expression is valid. Then we had two matches has_type_member<T, void> and has_type_member<T, int> , so which should we choose? That is why in the success case the type has to be void and then this overload is also chosen, since it is more special.

Why would a valid expression be evaluated to void, and how does this help us? Also, why does the expression need to be valid to match void_t?

So the second part of the first question I already answered. Regarding the first: Think of the definition of void_t :

template<class...>
using void_t = void;

So, ... matches everything regardless of type and number, doesn't it? Actually it only match a valid type, if not how would it potentially use this type? (I know it doesn't use the type, but it has to be able to. And it cannot use an invalid type). Therefore it gives us void if the template argument(s) passed are valid. So in our use case:

If T has a member type T::type , T::type is a valid type and void_t<...> matches it. So we get void_t<T::type> at this point, which evaluates to void , that fits to the primary but is more special, so we take it and get a true_type .

What about if we have no type member? Then the expression T::type is invalid, void_t<...> cannot mazch it and hence the partial specification is invalid, so we cannot chose it, but this no problem, since Substitution Failure Is Not An Error, so we just go on with what we already found, the primary template.

Is it even necessary to say that class equals to std::void_t<> ? Wouldn't it be enough to write

template< class, class = void > struct has_type_member : std::false_type { };

And if not, why?

Yes, it would, it is also done in the talk. void_t<> is literally void . I think the void_t is only taken to be more consistent with the second specification. There void_t is needed, since we need this template.l

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