7

The book C++ Templates : The Complete Guide has an example on page 275 that I'm having trouble wrapping my head around.

Quoting excerpts from the book...

template <typename T>
class Promotion<T,T> {
  public:
    typdef T ResultT;
};

template<typename T1, typename T2>
class Promotion<Array<T1>, Array<T2> > {
  public:
    typedef Array<typename Promotion<T1,T2>::ResultT> ResultT;
};

template<typename T>
class Promotion<Array<T>, Array<T> > {
  public:
    typedef Array<typename Promotion<T,T>::ResultT> ResultT;
};

Unfortunately, the partial specialization Promotion<Array<T1>, Array<T2> > is neither more nor less specialized than the partial specialization Promotion<T,T>. To avoid template selection ambiguity, the last partial specialization was added. It is more specialized than either of the previous two partial specializations.

Why are the first two templates ambiguous, and why does the last template solve the ambiguity issue? When I try to apply the rules I either can't figure out how it comes up with an ambiguity, or if I think I have a way for it to happen I don't know why the last template solves the problem.

sbi
  • 219,715
  • 46
  • 258
  • 445
person
  • 73
  • 3
  • 1
    Without the last specialization, what would be selected if we tried to use `Promotion, Array >`? – Anon. Dec 14 '10 at 01:42
  • So the last one fixes the problem because the compiler considers Promotion, Array > an exact match? – person Dec 14 '10 at 01:51

5 Answers5

15

Maybe your confusion stems from how the relation "is more specialised than" works. It's a partial order, not a total order -- that means that given 2 template specialisations, it's not always the case that one is more specialised than the other.

Anon's comment is right: Suppose that 3rd specialisation didn't exist, and later in your code you had:

Promotion<Array<double>, Array<double> > foo;

(Of course you probably wouldn't actually create a variable of this empty struct type, but this is just the simplest way to force its instantiation.)

Given this declaration of foo, which of the 1st 2 specialisations would be picked?

  • Specialisation 1 applies, with T = Array<double>.
  • Specialisation 2 applies, with T1 = double, T2 = double.

Both specialisations are applicable, so we need to determine which "is more specialised than" the other, and pick that one. How? We will say that X is more specialised than Y if it is at least as specialised as Y, but Y is not at least as specialised as X. Although it seems like this is just dancing around the problem, there is a clever rule that we can use to answer this new question:

X is at least as specialised as Y if, regardless of what types we assign to the template parameters of X, the resulting type could always be matched by Y.

Note that we forget about the particular types involved in the current instantiation (in this case, double) -- the "is at least as specialised as" relation is a property of the partial specialisations themselves, and doesn't depend on particular instantiations.

Can specialisation 1 always be matched by specialisation 2? The process is a bit like algebra. We require that for any type T, we can find types T1 and T2 such that:

Promotion<Array<T1>, Array<T2> > = Promotion<T, T>

This implies:

Array<T1> = T
Array<T2> = T

So the answer is no. Looking at just the first implied result, given any type T, in general it's not possible to find a type T1 such that Array<T1> is the same type as T. (It would work if T happened to be Array<long>, but not if T is int or char* or most other types.)

What about the other way around? Can specialisation 2 always be matched by specialisation 1? We require that for any types T1 and T2, we can find a type T such that:

Promotion<T, T> = Promotion<Array<T1>, Array<T2> >

Implying:

T = Array<T1>
T = Array<T2>

So the answer is again no. Given any type T1, it's always possible to find a type T such that T is the same type as Array<T1> -- just literally set T = Array<T1>. But in general the other type T2 is not constrained to be the same as T1, and if it's not (e.g. if T1 = bool but T2 = float) then it will not be possible to find a type T that is the same as both Array<T1> and Array<T2>. So in general, it's not possible to find such a type T.

In this case, not only is neither specialisation more specialised than the other, neither is even as specialised as the other. As a result, if the need arises to instantiate this template class and both specialisations match -- as it does in the example Anon gave -- there is no way to choose a "best" one.

j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • 3
    If I had sockpuppet accounts, I'd +1 this from each of them. Alas, this is my only account so I can only +1 total. – James McNellis Dec 14 '10 at 03:37
  • 1
    that's the first time I see a sane explanation of this, up until now I just guessed, and though it works fine (usually) I do appreciate having a rationale at hand! – Matthieu M. Dec 14 '10 at 07:40
  • I like this hands-on tutorial. Should this question be made into a FAQ entry about partial ordering of class template partial specializations? I think it fits well and has a nice answer. – Johannes Schaub - litb Dec 14 '10 at 10:38
3

It's because while deducing parameters, one template is better for the first parameter, but the other is better for the second.

Let's look at Promotion< Array<S>, Array<S> >.

Both candidates can match. On the first parameter, Promotion< T, T > matches by deducing T = Array<S>. Promotion< T1, T2 > matches by deducing T1 = S. The second one is a better match, since Array<T1> is more specific than T.

On the second parameter, Promotion< T = Array<S>, T = Array<S> > is an exact match. Promotion< T1 = S, T2 > matches by deducing T2 = S. Since Array<S> is a better match than Array<T2>, the first is more specific.

The rules for choosing the best template say that one parameter has to be a better match, and all the other be no worse, as compared to all other candidates. Since no candidate meets these criteria, it is ambiguous.

The third specialization does meet the criteria (first parameter is as good as Array<T1>, second is perfect), so it resolves the ambiguity.

If you REALLY want to make your head spin, try reading section [temp.deduct.partial] in the standard, where all these rules are stated in good lawyer-ese. It's 14.8.2.4 in draft n3225, numbering may vary in other versions.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • +1 but your explanation "at least one parameter has to be a better match, and the others no worse" is slightly off here -- that rule's used for deciding between overloaded functions in general, and it's only when that fails (i.e. when all arguments have equally good conversion sequences) that looking for most-specialised templates kicks in. Yes it's confusing as hell, had to check the standard myself ;) – j_random_hacker Dec 14 '10 at 03:57
  • @j_random_hacker: You're right, here it's template parameters not function arguments. And you definitely got the "no worse" part explained better in your answer, but ... the rule does actually seem to be quite similar to the overload resolution rule until you get deep into the details of "less specialized". – Ben Voigt Dec 14 '10 at 04:21
3

j_random_hacker has answered the question, but as a concrete example just using the standard's rules directly:

template< class T >
class Array {};

template< class T1, class T2 >
class Promotion {};

template <typename T>                   // a
class Promotion<T,T> {
public:
    typedef T ResultT;
};

template<typename T1, typename T2>      // b
class Promotion<Array<T1>, Array<T2> > {
public:
    typedef Array<typename Promotion<T1,T2>::ResultT> ResultT;
};

// template<typename T>
// class Promotion<Array<T>, Array<T> > {
//   public:
//     typedef Array<typename Promotion<T,T>::ResultT> ResultT;
// };


//---------------------------- §14.5.4.2/1:

template< class T >
void a_( Promotion< T, T > );                      // a

template< class T1, class T2 >
void b_( Promotion< Array< T1 >, Array< T2 > > );  // b


//---------------------------- §14.5.5.2/3:

class aT {};
class bT1 {};
class bT2 {};

void a( Promotion< aT, aT > );                          // a
void b( Promotion< Array< bT1 >, Array< bT2 > > );      // b

void test()
{
    // Check if the concrete 'a' arguments fit also 'b':
    b_( Promotion< aT, aT >() );
    // Fails, so a is not at least as specialized as b

    // Check if the concrete 'b' arguments fit also 'a':
    a_( Promotion< Array< bT1 >, Array< bT2 > >() );
    // Fails, so b is not at least as specialized as a
}    

Disclaimer: I had to re-teach myself this again.

I'm posting this because Someone Else™ found my example enlightening, so, might help also readers here.

Cheers & hth.,

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • @j_random_hacker: partial ordering for class templates is defined in terms of partial ordering for corresponding function templates... those are the function templates corresponding to the various specializations of `template class Promotion` – Ben Voigt Dec 14 '10 at 04:23
  • @Ben Voigt: That explains the function templates `a_()` and `b_()`, but not the non-template functions `a()` and `b()`. – j_random_hacker Dec 14 '10 at 04:31
  • @j_random_hacker: As you said in your own answer, the real types (which are present in `a_` and `b_`) get removed when developing the partial ordering... but I agree something funny is going on, since if you applied Alf's pattern to the three specialization code, it would still say they were ambiguous. Wait, no, in `test` it's using `a_` and `b_` again, so it's ok. – Ben Voigt Dec 14 '10 at 04:37
  • @Ben Voigt: I'm a bit confused... All I meant is that the non-template functions `a()` and `b()` are declared but never used anywhere -- wondering what Alf's intention was for them is all. – j_random_hacker Dec 14 '10 at 04:42
  • @j_random_hacker: Their formal arguments are used in the function calls down in `test`. – Ben Voigt Dec 14 '10 at 04:58
  • @Ben Voigt: What do you mean "their" formal arguments are used? Their parameters happen to have types that are the same as some types used in `test()`, but I don't see why that's important. If the 2 lines declaring `a()` and `b()` were deleted, absolutely nothing would change AFAIK. – j_random_hacker Dec 14 '10 at 06:29
  • @j_random_hacker: I just tried to follow the recipe in the standard exactly. it says "for each type template parameter, synthesize a unique type and substitute that for each occurrence of that parameter in the function parameter list" I should perhaps have put comment on that in addition to relevant C++98 para number. Should I? – Cheers and hth. - Alf Dec 14 '10 at 06:45
  • @Alf: I think I must be missing something obvious, because your explanation makes perfect sense to me, but (to me) only explains the existence of `aT`, `bT1`, `bT2`, `a_` and `b_` -- but not `a` and `b`. Sorry to harp on, but if you could explain why you need those 2 functions (`a` and `b`) when you already have the function templates `a_` and `b_` I'd appreciate it. – j_random_hacker Dec 14 '10 at 10:11
  • @j_random_hacker i think he just put `a` and `b` for illustration. An alternative way would be `template<> void b_( Promotion< aT, aT >);` and `template<> void a_( Promotion< Array< bT1 >, Array< bT2 > >() );`, which would both show the modified parameter type list and also do deduction. – Johannes Schaub - litb Dec 14 '10 at 10:48
  • It however would not work for non-deduced contexts. In partial ordering, non-deduced contexts are not compared (at least on several existing compilers - deduction IMO in general is underspecified in this area). For a call and an explicit specialization, deduction afterwards substitutes arguments into non-deduced contexts which could cause an error so I think they are not direct accurate translation of partial ordering. See http://llvm.org/bugs/show_bug.cgi?id=7708 – Johannes Schaub - litb Dec 14 '10 at 10:54
2

Ambiguity is when the same class can be instantiated from different specializations and compiler can't favor one specialization over the other one, because neither is more specialized. Adding a more specialized version makes compiler to choose that version. This resolves ambiguity, because C++ rules dictate that the most specialized template should be chosen over less specialized templates.

watson1180
  • 2,015
  • 1
  • 18
  • 24
2

Look at what the specializations do on a conceptual level.

The first one says "This is what we should do specifically if T1 and T2 are the same."

The second one says "This is what we should do specifically if T1 and T2 are both Array templates".

OK, so... let's say T1 and T2 are both the same array template. Which one should we use? There is no good way to argue that one of them is more specifically tailored to this situation than the other.

We resolve this by adding a third template specialization specifically for that situation: "This is what we should do specifically if T1 and T2 are both the same Array template".

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153