Why is void_t used in SFINAE?
Asked Answered
A

2

5

I intend for my question to be a follow up to: How do we use void_t for SFINAE?. I understand how void_t is used. I do not understand why it is necessary.

Let's take the example from that SO question and remove the void_t

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

// specialized as has_member< T , void > or discarded (SFINAE)
template< class T >
struct has_member< T , decltype( T::member ) > : std::true_type
{ };

If I pass in an object without 'member', it will still default to the default template, while an object with 'member' will instantiate the specialization.

Why is that thinking flawed? I think it has something to do with the second template parameter of the primary template having a default ('class = void'). But I don't see why that matters. Why does the specialization have to use a 'void_t' to match that default parameter? Isn't the whole point of default that they can be overridden? Where can I read more about that topic of cppreference.com?

Apicella answered 6/10 at 17:50 Comment(0)
G
4

Your primary template specifies, among other things, that the second template parameter defaults to void; so, for any type A, has_member<A> means has_member<A, void>.

Now, suppose that A has a public member named member of type int:

class A {
public:
    int member;
};

This means that decltype( T::member ) is int, so your template specialization provides a specialization for has_member<A, int>. That's totally valid! But it's not what you want — you want a specialization for has_member<A>, which is has_member<A, void>.

So with your version:

static_assert(has_member<A, int>::value, "A");   // succeeds
static_assert(has_member<A>::value, "A");        // fails

Does that make sense?

Gassy answered 6/10 at 18:15 Comment(3)
@DeanDeRosa there are no overloads for classes. Just specializations.Vizor
@DeanDeRosa: Almost! Your specialization of has_member<T, decltype(T::member)> is perfectly valid, it's just that it doesn't match has_member<A>, because has_member<A> means has_member<A, void> and decltype(A::member) is not void.Gassy
@DeanDeRosa: The process is explained at en.cppreference.com/w/cpp/language/template_parameters + en.cppreference.com/w/cpp/language/partial_specializationGassy
V
4

Where can I read more about that topic of cppreference.com?

It's just basic template specialization: Explicit (full) template specialization , Partial template specialization and Template parameters and template arguments

Isn't the whole point of default that they can be overridden?

Yes. But the way to override it is by explicitly giving a type parameter for it, like so:

has_member<A, int>::value
//            ^~~
//            explicit parameter, would choose the specialization

But when you don't specify it (as the template is supposed to be used):

has_member<A>::value

then the default one is used. So it becomes the provided default, i.e.:

has_member<A>::value
// is actually:
has_member<A, void>::value

And has_member<A, void> doesn't match your specialization (the one with std::true_type), so the generic template (the one with false_type) is used.

Vizor answered 6/10 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.