I’m trying to have a different template specialization for classes which have an inner class with a particular name present. I’ve taken a clue from here and tried the following:
#include <iostream>
template< typename T, typename Check = void > struct HasXYZ
{ static const bool value = false; };
template< typename T > struct HasXYZ< T, typename T::XYZ >
{ static const bool value = true; };
struct Foo
{
class XYZ {};
};
struct FooWithTypedef
{
typedef void XYZ;
};
int main()
{
// The following line prints 1, as expected
std::cout << HasXYZ< FooWithTypedef >::value << std::endl;
// The following line prints 0. Why?
std::cout << HasXYZ< Foo >::value << std::endl;
return 0;
}
As you can see, if I test for a typedef-defined type in FooWithTypedef, it works. However, it does not work if the type is a genuine inner class. It also only works when the typedef-ed type in FooWithTypedef matches the default value of the second argument in the initial template declaration (which is void in my example). Could one explain what is going on here? How does the specialization process work here?
Answer to the initial question
The template specialization you defined here:
will take effect when somebody uses the data type
HasXYZ<A,A::XYZ>for some data typeA.Note that, whatever
Ais,A::XYZis a data type totally independent ofA. Inner classes are data types in their own right. When you useAas the first template argument, there is absolutely no reason for the compiler to assume that you want to use something calledA:XYZas the second argument, even if an inner class of that name exists, and even if doing so would lead the compiler to a template specialization that matches the template arguments exactly. Template specializations are found based on the template arguments provided by the coder, not based on further possible template arguments.Hence when you use
HasXYZ<Foo>, it falls back to using the default template argumentvoidfor the second parameter.Needless to say that if you were to use
HasXYZ<Foo,Foo:XYZ>explicitly, you’d get the expected output. But that obviously is not what you intended.I am afraid the only way to get what you need is
std::enable_if(or something that works in a similar way).Answer to the additional question (after update)
Consider the simplification below:
The primary definition specifies a default argument of
voidfor the second template parameter. But the specialization (second definition above) defines whatclass Aactually looks like if the second template parameter really isvoid.What this means is that if you use, say,
A<int>in your code, the default argument will be supplemented so you getA<int,void>, and then the compiler finds the most fitting template specialization, which is the second one above.So, while default template arguments are defined as part of the primary template declaration, making use of them does not imply that the primary template definition is used. This is basically because default template arguments are part of the template declaration, not the template definition (*).
This why in your example, when
typedef void XYZis included inFooWithTypedef, the second template parameter defaults tovoidand then the most fitting specialization is found. This works even if in the template specialization the second argument is defined asT::XYZinstead ofvoid. If these are the same at the time of evaluation, the template specialization will be selected (§14.4 "Type equivalence").(*) I didn’t find a statement in the Standard that actually says it so clearly. But there is §14.1/10, which describes the case where you have multiple declarations (but only one primary definition) of a template:
This suggests that the mechanism behind default template arguments is independent of that used to identify the most fitting specialization of the template.
In addition, there are two existing SO posts that refer to this mechanism as well:
This reply to Template specialization to use default type if class member typedef does not exist
Default values of template parameters in the class template specializations