I have the following code, which may seem convoluted but comes from real code:
#include <iostream>
using namespace std;
template <class Hrm, class A>
void foo(Hrm& h, A& a)
{
cout << "generic" << endl;
}
template <template <bool> class Hrg>
void foo(Hrg<false>& h, int& a)
{
cout << "specialized int" << endl;
}
template <template <bool> class Hrg>
void foo(Hrg<true>& h, const int& a)
{
cout << "specialized const-int" << endl;
}
template <bool W>
struct what;
template<> struct what<true> { };
template<> struct what<false> { };
int main() {
what<true> wt;
what<false> wf;
int i = 5;
const int& ri = i;
foo(wt, i); // 1) generic
foo(wf, i); // 2) specialized int
foo(wt, ri); // 5) specialized const-int
foo(wf, ri); // 6) generic
return 0;
}
I understand 4
: there is no specialization for a false Hrg
with a const int
, so the generic version is called.
My question is, why are the given functions called for the other cases? 3
seems to called the specialized const version because const int
matches "more directly" than A
. I'd like to know why that happens more specifically.
And, what about 1
and 2
? Particularly, 1
very surprising to me: why is the generic
version called instead of specialized const-int?
An additional note: if I change the two foo
specializations to:
template <template <bool> class Hrg>
void _foo(Hrg<false>& h, int& a)
{
cout << "specialized int" << endl;
}
template <template <bool> class Hrg>
void _foo(Hrg<true>& h, const int& a)
{
cout << "specialized const-int" << endl;
}
template <class Hrg>
void foo(Hrg& h, int& a)
{
return _foo(h, a);
}
template <class Hrg>
void foo(Hrg& h, const int& a)
{
return _foo(h, a);
}
Then the output becomes:
foo(wt, i); // a) specialized const-int
foo(wf, i); // b) specialized int
foo(wt, ri); // c) specialized const-int
//foo(wf, ri); // d) compilation error
Which is a much more intuitive result for me.
Overload resolution occurs in the following steps:
It's important to remember that step 4 comes after step 3; the "genericness" or "templateness" is exclusively a tie-breaker rule.
Let's go over all your examples in your first code block.
(1) Deduction succeeds on the first and third overloads; Hrg
cannot be deduced for the second. The candidates are therefore the first and third (rule 1). Both are viable (rule 2). The first overload would bind i
to int&
while the third would bind i
to const int&
. Binding to the less cv-qualified reference is preferred (rule 3). (Barry has the specific quote from the standard.) The first (generic) overload wins.
(2) Hrg
cannot be deduced for the third overload, so it is not a candidate (rule 1). The first and second are candidates and are viable (rule 2). The first and second overloads both match exactly with no conversions required and are indistinguishable by rule 3. The second wins because it's more specialized (rule 4).
(5) Deduction of Hrg
fails for the second overload, so it is not a candidate, while the first and third are (rule 1). Note that for the first overload, A
is deduced as const int
, producing an identical signature to the third overload. They are both viable (rule 2) and are indistinguishable by the end of rule 3. The third overload wins because it's more specialized (rule 4).
(6) Deduction of Hrg
fails for the third overload, so it is not a candidate, while the first and second are (rule 1). The second overload is not viable (rule 2) since int&
cannot bind to ri
, which is const
. The first overload, the generic one, is the only viable function, so it wins.
I leave the overload resolution in the second code block as an exercise for the reader.
[1] As T.C. points out in comments, there is a subtlety here. The tie-breaker rule only applies when, for a given pair of functions, the implicit conversion sequences required to initialize the parameters from the arguments are equally ranked for every pair of corresponding parameters. If the first function has a better implicit conversion sequence for one parameter and the second has a better implicit conversion sequence for a different parameter, the tie-breaker rule isn't applied, and the ambiguity remains. This case doesn't occur in the example in the question, though.