Question

#include <type_traits>

using namespace std;

template<class T> struct IsCharType          { enum { value = false }; };    
template<>        struct IsCharType<char>    { enum { value = true }; };    
template<>        struct IsCharType<wchar_t> { enum { value = true }; };

template<class CharType, class = enable_if<IsCharType<CharType>::value>::type>
struct MyString
{
    MyString()
    {}

    MyString(CharType*)
    {}
};

template<class CharType>
MyString<CharType> GetMyString(CharType* str) // error C2923
{
    return MyString<CharType>(str);
}

int main()
{}

My compiler is VC++ 2013 RC. The code above cannot be compiled because of the error C2923.

error C2923: 'MyString' : 'std::enable_if::value,void>::type' is not a valid template type argument for parameter ''

However, if I change

template<class CharType, class = enable_if<IsCharType<CharType>::value>::type>

to

template<class CharType, class = typename enable_if<IsCharType<CharType>::value>::type>

then it will be OK.

I just wonder why typename is necessary here? In this case, I have two reasons:

  1. The compiler can deduce if the second template parameter is a type name; Because I decalred it as class.

  2. Even if the compiler cannot deduce if it is a type name, as per the SFINAE rule, the compiler should not fail because the template function GetMyString is not called.

Was it helpful?

Solution

You have not declared it as class. Your template parameter declaration

class = enable_if<IsCharType<CharType>::value>::type

is a declaration of an unnamed type parameter. We can give this parameter an explicit name, e.g.

class U = enable_if<IsCharType<CharType>::value>::type

So in this case it is U that is declared as type parameter ("as class").

Meanwhile, everything to the right of = is not "declared as class". It is interpreted by the compiler in accordance with general rules, without any regard to the fact that it happens to be a default argument for a type parameter.

Logic like "since this is a default argument for a type parameter, the compiler must realize by itself that this is a type" does not apply here simply because the language specification does not say it applies. Moreover, the language standard actually says

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

This means that the compiler is required to assume that

enable_if<IsCharType<CharType>::value>::type

is not a type, which is contrary to what you expect it to do.

The language does have a number of contexts where dependent name is unconditionally assumed to name a type. That includes constructor initializer lists, base class specifiers and elaborated type specifiers. But default arguments for template type parameter is not one of such contexts.

SFINAE does not save the situation here. SFINAE, as the name suggests, works at the time of substitution. Your code never gets to substitution, it breaks much earlier. It is simply an invalid template definition.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top