I have the following class structure
// file foo.h:
struct foo_base
{ ... }
template<typename T> struct foo : foo_base
{ ... };
template<typename F>
using is_foo = std::is_convertible<F,foo_base>;
template<typename, typename=void> struct aux;
template<typename Foo>
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
{ ... }; // specialisation for any foo
// file bar.h:
#include "foo.h"
template<typename T> struct bar : foo<T>
{ ... };
template<typename T>
struct aux<bar<T>>
{ ... }; // specialisation for bar<T>
Now, the problem is that for aux<bar<T>>
both specialisations provided for aux
are viable. Is there a way to avoid this disambiguity without providing another specialisation for every T
? Note that modifications to file foo.h
must not know about file bar.h
.
Note The ambiguity shall be resolved such that the specialisation provided in file bar.h
is picked for any aux<bar<T>>
. Originally, bar
was not a template and the specialisation aux<bar>
not partial and hence preferred. The problem arose by making bar
a template.
The compiler doesn't see struct aux<bar<T>>
as more specialised than struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
because of second template argument. You can specify the second argument the same way in your bar<T>
specialisation:
template<typename T>
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>
{ };
The rules for how specialised partial template specialisations are are complicated, but I will try to explain very briefly:
The three (your two, plus my one) relevant specialisations are
template<typename Foo>
struct aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>
template<typename T>
struct aux<bar<T>> // or aux<bar<T>, void>
{ };
template<typename T>
struct aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>
{ };
Per the standard (14.5.5.2), to determine which of the class template partial specialisations is the most specialised, the question that needs to be answered is which of the following function template overloads would be the best match in a call to f(aux<bar<T>>())
:
template<typename Foo>
void f(aux<Foo, typename std::enable_if<is_foo<Foo>::value>::type>); // 1
template<typename T>
void f(aux<bar<T>>); // or aux<bar<T>, void> // 2
template<typename T>
void f(aux<bar<T>, typename std::enable_if<is_foo<bar<T>>::value>::type>); // 3
And there, in turn, the partial ordering rules for functions say that 1 is not more specialised than 2, and that 2 is not more specialised than 1, roughly speaking, because 1 is not clearly more specialised than 2, and 2 is not clearly more specialised than 1. "Clearly more specialised" is not how the standard words it, but that essentially means that based on the type arguments of one of those, the type arguments of the other are not deducible.
When comparing 1 and 3, however, the arguments of 1 are deducible from 3: Foo
can be deduced as bar<T>
. Therefore, 3 is at least as specialised as 1. However, the arguments of 3 are not deducible from 1: T
cannot be deduced at all. The compiler's conclusion therefore is that 3 is more specialised than 1.