5

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.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Walter
  • 44,150
  • 20
  • 113
  • 196

2 Answers2

4

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.

  • Wow. That seems to work. But I don't quite understand how/why. If I simply specialise as `struct aux, void>` it doesn't work. Perhaps you can give some more explanation/insight. – Walter Jun 10 '14 at 07:56
  • @Walter I've attempted to explain, but it's a very complicated subject, and I may have made some mistakes. Please take a look and see if it helps. –  Jun 10 '14 at 08:33
  • @hvd +1 but see my solution for a way to also allow further derived classes to match the pattern. – TemplateRex Jun 10 '14 at 09:08
2

Partial specialization of class templates is based on pattern matching. In contrast, customizing function templates is based on template argument deduction and overload resolution.

Because of the class hierarchy present in your problem, customizing behavior would in principle be more convenient through function template overloading, because that can take derived-to-base conversions into account. The pattern matching used in partial class template specialization does not provide the same flexibility.

However, since C++11, it is possible to do compile-time return type deduction. Here is a solution that combines tag dispatching, default constructors and decltype type deduction:

#include <iostream>

// file foo_base.h:

struct foo_base 
{ 
    foo_base() = default; 
};

foo_base faux(foo_base const&)
{ 
    return foo_base{}; 
}

template<class T, class = decltype(faux(T{}))>
struct aux;

template<class T>
struct aux<T, foo_base> 
{ 
    enum { value = 1 }; 
};

// file foo.h:

template<typename T> 
struct foo : foo_base  
{ 
    foo() = default; 
};

// file bar.h:

template<typename T> 
struct bar : foo<T> 
{ 
    bar() = default; 
};

template<class T>
bar<T> faux(bar<T> const&) 
{ 
    return bar<T>{}; 
}

template<class T, class U>
struct aux<T, bar<U>> 
{ 
    enum { value = 2 }; 
};

// file meow.h

template<class T>
struct meow : bar<T>
{
    meow() = default;    
};

int main()
{
    std::cout << aux<foo_base>::value;  // 1
    std::cout << aux<foo<int>>::value;  // 1
    std::cout << aux<bar<int>>::value;  // 2
    std::cout << aux<meow<int>>::value; // 2
}

Live Example that works with both g++ and clang in C++11 mode (C++14 mode is not required!).

The constexpr function faux() is overloaded for foo_bar and as a function template for bar<T>. Any argument whose class is derived from foo_base but not from bar<T> will select the former overload, and anything derived from bar<T> will select the latter overload. This mechanism is the same as e.g. in the Standard Library where iterator categories are used to tag dispatch several implementations of std::advance() e.g.

To use this selection mechanism during partial specialization of your class template aux, two more ingredients are required. First, all classes are required to have a default constructor. Secondly, decltype() is applied to that expression faux(T{}) to deduce the return type.

NOTE: it is not required that faux() is constexpr or that any of the default constructors are constexpr, because decltype() will not actually evaluate the function call but only deduce its return type.

The main class template aux has a default template argument:

template<class T, class = decltype(faux(T{}))>
struct aux;

Partially specializing the second argument on foo_base allows you to provide behavior for any class that derives from foo_base:

template<class T>
struct aux<T, foo_base> 
{ // custom behavior for anything derived from foo_bar };

The second partial specialization matches any class derived from any template instantiation bar<U>

template<class T, class U>
struct aux<T, bar<U>> 
{ // custom behavior for anything derived from bar<U> for some U }

NOTE: the main drawback is that you might need to provide a default constructor to all classes in your hierarchy. This may or may not be an obstacle that you can overcome. Most classes already have a default constructor, but some may not. In that sense this solution is intrusive (i.e. it cannot be bolted on top of existing code, but it requires modification of that code).

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Clever answer. It assumes that classes derived from `bar` should be matched, even though the OP's code wouldn't match them, but that may well be a correct assumption. –  Jun 10 '14 at 09:11
  • @hvd I just discovered that `constexpr` is not even required, because `decltype` doesn't actually evaluate the function call, it just deduces the return type! – TemplateRex Jun 10 '14 at 09:12
  • @hvd it's quite easy to only match `bar`, just provide a `aux>` partial specialization. – TemplateRex Jun 10 '14 at 09:17
  • Sure, but if you only need to match `bar`, your answer is more complicated than mine for no benefit. The benefit of your answer lies in its flexibility that allows it to match derived classes too. :) –  Jun 10 '14 at 09:22
  • @hvd yes, I like your solution as well. I was mainly looking to map a set of function overloads to a set of class specializations. The other way around is idiomatic of course because it's a 1-to-many mapping through `template auto fun(T const& t) { return helper_class()(t); }`. Here it's a faithfull many-to-many mapping. – TemplateRex Jun 10 '14 at 09:25
  • @hvd IOW, the real benefit to me is that the entire *class hierarchy* is reflected in the set of partial specializations, just as it would be with the usual tag dispatching for function templates – TemplateRex Jun 10 '14 at 09:27
  • I think that's what I was saying (or at least trying to say) too, but I get confused easily. I think we're in agreement, anyway, and I do think your answer is more powerful and useful in general than mine. –  Jun 10 '14 at 09:36