Why do we need VoidT to enable SFINAE ?

Hi,

I don't get it. Why do we need VoidT to enable SFINAE in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// helper to ignore any number of template parameters: 
template<typename...> using VoidT = void;

// primary template:
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type
{
};

// partial specialization (may be SFINAE'd away):
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
{
};


I try removing the VoidT and leaving code like so but then it doesn't recognize when T defines size_type (why??)

1
2
3
4
template<typename T>
struct HasSizeTypeT<T, typename T::size_type> : std::true_type
{
};


This last version is never instantiated - for all types (even having size_type) the primary template is chosen.

This must be some conceptual aspect of SFINAE that I have not yet understood and thus it is very important and will be very grateful for an explanation of the role of VoidT in making SFINAE possible.


Regards as always,
Juan Dent
> it doesn't recognize when T defines size_type
> This last version is never instantiated

The code does not compile. The attempted specialisation has more template arguments than the primary template.
http://coliru.stacked-crooked.com/a/e5a42951f6e196ba

The second template parameter in the primary template is required because:
Partial template specializations are not found by name lookup. Only if the primary template is found by name lookup, its partial specializations are considered.
http://en.cppreference.com/w/cpp/language/partial_specialization


Note that these template definitions would compile cleanly; this is overloading, not specialisation.
1
2
3
4
template < typename T > constexpr bool has_size_type() { return false ; }

template < typename T, typename = typename T::size_type >
constexpr bool has_size_type() { return true ; }


However, this would result in an ambiguity during overload resolution:
constexpr bool flag = has_size_type<std::string>() ;
> The attempted specialisation has more template arguments than the primary template

But both primary template and specialization have 2 template arguments???

I may have not been clear in my question. What I don't understand is why SFINAE works for:

1
2
3
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
{};


but not for:

1
2
3
template<typename T>
struct HasSizeTypeT<T, typename T::size_type> : std::true_type
{};


VoidT becomes void always doesn't it? Why is it necessary for SFINAE?

Regards JLBorges!

Juan
This is related to specializations inheriting defaults from the primary template:

https://stackoverflow.com/questions/18700558/default-template-parameter-partial-specialization

Void wasn't chosen for any particular reason. e.g. this would also work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// helper to ignore any number of template parameters: 
struct Something{};
template<typename...> using SomethingT = Something;

// primary template:
template<typename, typename = Something>
struct HasSizeTypeT : std::false_type
{
};

// partial specialization (may be SFINAE'd away):
template<typename T>
struct HasSizeTypeT<T, SomethingT<typename T::size_type>> : std::true_type
{
};


So if your instantiation is say HasSizeTypeT<std::string> then after the defaults are applied you are looking for the best specialization that matches HasSizeTypeT<std::string, void>. This is the second HasSizeTypeT which specializes <T, void>. For std::string, this would pass SFINAE.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <iomanip>
#include <string>

template< typename, typename = void > struct has_size_type : std::false_type {};

template< typename T >
struct has_size_type< T, std::void_t<typename T::size_type> > : std::true_type {};

namespace bad
{
    template< typename, typename = void > struct has_size_type : std::false_type {};

    template< typename T >
    struct has_size_type< T, typename T::size_type > : std::true_type {};
}

int main()
{
    constexpr bool a = has_size_type<std::string>::value ;
    // 1. name look up finds the primary template has_size_type<typename,typename>
    //        with template args: has_size_type< std::string, void >
    // 2. look for specialisations that match has_size_type< std::string, void >
    // 3. complete specialisation for has_size_type< std::string, void > : not found
    // 4. look for partial specialisations that match has_size_type< std::string, void >
    // 5. found partial specialisation has_size_type< T, std::void_t<typename T::size_type> >
    //        matches has_size_type< std::string, void >
    // 6. there are no other partial specialisations; select this specialisation
    static_assert( a, "error: std::string must have a nested type/type alias size_t" ) ;

    constexpr bool b = bad::has_size_type<std::string>::value ;
    // 1. name look up finds the primary template finds bad::has_size_type<typename,typename>
    //        with template args: bad::has_size_type< std::string, void >
    // 2. look for specialisations that match bad::has_size_type< std::string, void >
    // 3. complete specialisation for bad::has_size_type< std::string, void > : not found
    // 4. look for partial specialisations that match bad::has_size_type< std::string, void >
    // 5. found partial specialisation bad::has_size_type< T, typename T::size_type >
    //        no match:      bad::has_size_type< std::string, std::string::size_type >
    //        does not match bad::has_size_type< std::string, void > so ignore this specialisation
    // 6. there are no other partial specialisations
    // 7. no matching specialisations, so select the primary template
    static_assert( !b, "error: b must be false (primary template must have been selected)" ) ;
}

http://coliru.stacked-crooked.com/a/a050d9757c0ade8f
Thanks JLBorges for clarifying how the lookup occurs!

Regards,
Juan Dent
When I made the post, I hadn't noticed that elohssa had already answered the question.

I'll let it remain;
though it just annotates what elohssa had already said, some programmers may find the elaboration useful.
Registered users can post here. Sign in or register to post.