Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template selection

Given the following code :

#include <memory>
#include <iostream>

using namespace std;

template<typename T>
void test(T & value) {
  cout << "most generic" << endl;
}

template<typename T>
void test(shared_ptr<T> & value) {
  cout << "shared_ptr" << endl;
}

class A {};

int main(int argc, char ** argv) {
  A a;
  shared_ptr<A> p(new A());
  test(a);
  test(p);
  return 0;
}

Why is the call

test(p)

instantiating the second form of test with T = A instead of complaining that it cannot distinguish between the two signatures ?

like image 475
TiMoch Avatar asked Mar 19 '13 10:03

TiMoch


Video Answer


1 Answers

Because even though they are both viable choices for overload resolution, the second function template is more specialized than the first one.

Per Paragraph 13.3.3/1 of the C++11 Standard:

[...] Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type. [...] or, if not that,

— F1 is a non-template function and F2 is a function template specialization, or, if not that,

— F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

§ 14.5.6.2 then says how a function template is determined to be more specialized than another function template. In particular, per 14.5.6.2/2:

Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

Formal definitions in the Standard can be pretty hard to decipher, but their complexity is normally meant to unambiguously make the language behave like we would naturally expect in most situations.

The intuitive expectations we could have about the overloads you provide is that the one accepting a std::shared_ptr<T> should be selected when the argument is of type std::shared_ptr<int>, because it appears to be dealing with std::shared_ptr<> objects specifically and, therefore, it looks like a better (more specialized) candidate than the unconstrained overload.

The formal procedure for translating this intuitive expectation into an unambiguous set of rules may sound complex, but following it is not particularly hard in our situation, where we want to determine if this overload:

template<typename T>
void f(std::shared_ptr<T>);

Is more specialized than this:

template<typename U>
void f(U);

Although in this case it is easy for us to tell which one is more specialized just based on our intuition, a compiler must rely on an algorithm, and this algorithm must work in all situations.

In this case, the mechanism would go as follows:

  1. Take the first overload, substitute its template parameter T for a type argument (any type argument), for instance int, and instantiate the corresponding signature - the function parameter would have type std::shared_ptr<int>;
  2. Is it always possible to call the second overload by providing an object of that type (shared_ptr<int> in this case) as its input, and deduce the type U from it?
  3. Well, the answer is Yes. U will just be deduced to be std::shared_ptr<int>;
  4. The other way round now: Take the second overload, substitute its template parameter U for any type argument, for instance bool, and instantiate the corresponding signature - the function parameter would have type bool;
  5. Is it always possible to call the first overload by providing an object of that type (bool) as its argument and deduce the type T from it?
  6. The answer here is No, of course. There is no way to deduce T so that std::shared_ptr<T> would match bool;
  7. Conclusion: The first overload is more specialized than the second.

Of course, things get slightly more complicated when there is more than one template parameter and more than one function parameter, but the mechanism is pretty much the same.

like image 184
Andy Prowl Avatar answered Nov 02 '22 19:11

Andy Prowl