4

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?

Maki
  • 177
  • 7

1 Answers1

1

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

n314159
  • 4,990
  • 1
  • 5
  • 20
  • Ok, the first part makes sense. The first parameter is necessary, while the second is optional, and it's just defaulted to std::void_t<>. Meaning, this will match anything up to two parameters by default, and then, we'll check if there's a more suitable option for our parameters. I wrote the definition of void_t<> wrong, so I'll change it to: `template using void_t = void;` And, from what I noticed, it matches any valid type, not object. You can use decltype on an object, and similar tricks with decltype (like std::decay, etc.). – Maki Dec 11 '19 at 19:26
  • I think that I was looking wrong at the "gives us void if the type is valid". After finding this [stackoverflow post](https://stackoverflow.com/questions/34288844/what-does-casting-to-void-really-do), I realized that we won't convert the value to void (which is impossible), we'll just discard it, or void it. Because casting to void discards the expression. The passed type still needs to be an actual type, which makes sense (especially when you look at how parsers work). – Maki Dec 11 '19 at 19:39
  • Also, I guess, I wasn't clear with this part: `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...`. I thought that I can use the expression that follows this question to match any number of parameters, because of the defaulted template parameter. Looking back at it now, it makes sense that something like that is impossible given those circumstances. – Maki Dec 11 '19 at 19:43
  • I understand that void_t is just an alias to void, which makes it clearer that it's supposed to be used with templates. And now, I understand that void_t can match any number of arguments as well, and just void them. But, I don't see why we wouldn't just use something like `template` instead. – Maki Dec 11 '19 at 19:48
  • If you are asking, why we don't define `template using void_t = void;`, then my answer is: that is the same. I only copied the raw `...` from the talk (I am actually not sure if that is just a shorthand, an error in the talk or maybe a compiler extension, looking it up I found no source regarding whether `template<...>` is allowed). The standard [defines](https://en.cppreference.com/w/cpp/types/void_t) it with `template`. I'll edit that in my answer. Is something else unclear? @Maki – n314159 Dec 11 '19 at 22:04