How does C ++ allow specialized patterns based on constant, patterns, and pedigree?

I have the following code, which may seem confusing, 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; } 

The perfect link .

I understand 4 : there is no specialization for false Hrg with const int , so the generic version is called.

My question is: why are the given functions called for other cases? 3 seems to be called the specialized version of const, because const int matches "more directly" than A I would like to know why this is happening more specifically.

What about 1 and 2 ? In particular, 1 very surprising to me : why is the generic version called instead of the specialized const-int?


Additional note: if I changed 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 will be:

 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 

This is a much more intuitive result for me.

+6
source share
2 answers

Overload resolution occurs in the following steps:

  • A set of candidate functions is being assembled. This set of candidates consists of both non-template functions and specializations of function templates. If the deduction of the template parameter is not performed on the function template, it is silently deleted from the set of candidates.
  • A subset of candidate functions is defined as viable. This means that the number of parameters it has is compatible with the number of arguments and that each argument can be implicitly converted to the corresponding parameter type. (Note that the function, if it is viable, even if the conversion is ambiguous, but in this case this function can still be selected, and then you get a compilation error).
  • Viable functions are compared. For a given pair of viable functions, one that in a sense requires a less implicit conversion to initialize function parameters from the given arguments is considered β€œbetter” than another function. Please note that it is possible that given two functions, neither is better than the other. Often, these rules are sufficient to establish that one viable function is better than all the others. This function will then receive overload permission.
  • If there are two functions, and none of them is better than the other, then in some cases [1] the tie-break rule is applied, which may still determine that it is better than the other. If rule 3 cannot determine which of the two viable functions is better, but only one of them is not a template, a non-template is better; if both are template specializations, but one of them is created from a more specialized template than this function is better. After a tie-breaker, if there is one better viable function (better than everyone else), this function wins overload resolution. If the ambiguity remains, overload resolution fails and the call is ambiguous.

It is important to remember that step 4 comes after step 3; "pedigree" or "stereotyped" exclusively tie-break rules.

Skip all your examples in the first block of code.

(1) The deduction succeeds at the first and third overloads; Hrg cannot be bred for the second. Thus, the candidates are first and third (rule 1). Both are viable (rule 2). The first overload will bind i to int& , and the third will bind i to const int& . Linking with a less qualified reference is preferred (rule 3). (Barry has a specific quote from the standard.) The first (general) overload wins.

(2) Hrg cannot be displayed for the third overload, therefore it is not a candidate (rule 1). The first and second candidates are viable (rule 2). The first and second overloads coincide exactly without the necessary transformations and are indistinguishable by rule 3. The second wins because it is more specialized (rule 4).

(5) Hrg output is not performed for the second overload, therefore, it is not a candidate, and the first and third - (rule 1). Note that for the first overload, A is output as const int , creating an identical signature for the third overload. They are both viable (rule 2) and indistinguishable towards the end of rule 3. The third overload wins because it is more specialized (rule 4).

(6) The deduction of Hrg not suitable for the third overload, therefore it is not a candidate, and the first and second - (rule 1). The second overload is not viable (rule 2), since int& cannot communicate with ri , which is equal to const . The first overload, general, is the only viable function, so it wins.

I leave the overload resolution in the second block of code as an exercise for the reader.

[1] As TC points out in the comments, there is subtlety here. The tie-break rule applies only when, for a given pair of functions, the implicit conversion sequences needed to initialize the parameters from the arguments are evaluated equally for each 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 another parameter, the tie-break rule is not applied, and the uncertainty remains. However, this case does not occur in the example in the question.

+4
source

OK in four different test cases. In all cases, the conversion sequences are all the same - no conversions are required, so we need to move on to the next step of overload resolution.

 foo(wt, i); // 1) generic 

There are two potential overloads here. All and 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):

The standard conversion sequence S1 is a better conversion sequence than the standard conversion sequence S2 if ... - S1 and S2 are the binding bindings (8.5.3), and the types to which the links refer are the same type, except for the top-level cv qualifiers , and the type to which the link referenced, initialized by the S2 sign, is more qualified than the type to which the link initialized by the S1 sign refers.

const int larger than CV than int , so int overloading is preferred - which will be shared.

 foo(wf, i); // 2) specialized int 

There are two overloads here:

 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 the same here, so nothing in this section can distinguish one from the other. So, we go to [over.match.best]:

Given these definitions, a viable function F1 is defined as a better function than another viable function F2 if for all arguments i, ICSi ( F1 ) is no worse than the transformation scheme than ICSi ( F2 ), and then ...
- F1 and F2 are specialized function templates, and the function template for F1 is more specialized than the template for F2 in accordance with the partial ordering rules described in 14.5.6.2.

The rules for "more specialized than" are complex, but basically mean that the second overload is viable for a strict subset of the types for which the first is viable, so 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 identical, but the second overload is more specialized than the first; therefore, specialized overload is preferable for the same reasons as in (2).

 foo(wf, ri); // 6) generic 

Here, "general" congestion is the only viable congestion.

UPDATE . The new tests you added are simpler than the previous four. Given 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 possible. But when called with i , the first overload is preferred for the same reasons as (1) above - int less than cv-qual than const int .

+2
source

Source: https://habr.com/ru/post/986607/


All Articles