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;
}
Ideone link.
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.
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.
It is possible in C++ to get a special behavior for a particular data type. This is called template specialization. Template allows us to define generic classes and generic functions and thus provide support for generic programming.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
Class templates are generally used to implement containers. A class template is instantiated by passing a given set of types to it as template arguments. The C++ Standard Library contains many class templates, in particular the containers adapted from the Standard Template Library, such as vector .
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.
Going in order through the four distinct test cases. In all cases, the conversion sequences are all the same - there are no conversions required, so we would have to move on to the next stage of overload resolution.
foo(wt, i); // 1) generic
There are two potential overloads here. The everything and the Hrg<true>
template <class Hrm, class A> void foo(Hrm& h, A& a);
template <template <bool> class Hrg> void foo(Hrg<true>& h, const int& a);
In [over.ics.rank], we have (thanks @dyp):
Standard conversion sequence
S1
is a better conversion sequence than standard conversion sequenceS2
if...
—S1
andS2
are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized byS2
refers is more cv-qualified than the type to which the reference initialized byS1
refers..
const int
is more cv-qualified than int
, so the int
overload is preferred - which would be the generic one.
foo(wf, i); // 2) specialized int
Here the two overloads are
template <class Hrm, class A> void foo(Hrm& h, A& a)
template <template <bool> class Hrg> void foo(Hrg<false>& h, int& a)
Both conversion sequences are identical here, so nothing in that section can distinguish one from the other. So we move onto [over.match.best]:
Given these definitions, a viable function
F1
is defined to be a better function than another viable functionF2
if for all arguments i, ICSi(F1
) is not a worse conversion sequence than ICSi(F2
), and then
— ...
—F1
andF2
are function template specializations, and the function template forF1
is more specialized than the template forF2
according to the partial ordering rules described in 14.5.6.2.
The rules concerning "more specialized than" are complicated but basically mean that the second overload is viable for a strict subset of the types for which the first is viable for, hence it is preferred.
foo(wt, ri); // 5) specialized const-int
Here, we have the same two overloads as in the first case:
template <class Hrm, class A> void foo(Hrm& h, A& a);
template <template <bool> class Hrg> void foo(Hrg<true>& h, const int& a);
The conversion sequences are both identical, but the second overload is more specialized than the first, so the specialized overload is preferred for the same reasons as it was in (2).
foo(wf, ri); // 6) generic
Here the "generic" overload is the only viable overload.
UPDATE The new tests you added are more straightforward than the previous four. Given the two foo
overloads:
template <class Hrg> void foo(Hrg& h, int& a);
template <class Hrg> void foo(Hrg& h, const int& a);
When called with ri
, only the second overload is viable. But when called with i
, the first overload is preferred for the same reasons as (1) above - int
is less cv-qualified than const int
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With