B.2 Simplified Overload Resolution

Ru-Brd

Overload resolution ranks the viable candidate functions by comparing how each argument of the call matches the corresponding parameter of the candidates. For one candidate to be considered better than another, the better candidate cannot have any of its parameters be a worse match than the corresponding parameter in the other candidate. The following example illustrates this:

 void combine(int, double);  void combine(long, int);  int main()  {      combine (1, 2);  // ambiguous!  } 

In this example, the call to combine() is ambiguous because the first candidate matches the first argument (the literal 1 of type int ) best , whereas the second candidate matches the second argument best . We could argue that int is in some sense closer to long than to double (which supports choosing the second candidate), but C++ does not attempt to define a measure of closeness that involves multiple call arguments.

Given this first principle, we are left with specifying how well a given argument matches the corresponding parameter of a viable candidate. As a first approximation we can rank the possible matches as follows (from best to worst):

  • Perfect match. The parameter has the type of the expression, or it has a type that is a reference to the type of the expression (possibly with added const and/or volatile qualifiers).

  • Match with minor adjustments. This includes, for example, the decay of an array variable to a pointer to its first element, or the addition of const to match an argument of type int** to a parameter of type int const* const* .

  • Match with promotion. Promotion is a kind of implicit conversion that includes the conversion of small integral types (such as bool , char , short , and sometimes enumerations) to int , unsigned int , long or unsigned long , and the conversion of float to double .

  • Match with standard conversions only. This includes any sort of standard conversion (such as int to float ) but excludes the implicit call to a conversion operator or a converting constructor.

  • Match with user -defined conversions. This allows any kind of implicit conversion.

  • Match with ellipsis. An ellipsis parameter can match almost any type (but non-POD class types result in undefined behavior).

The following contrived example illustrates some of these matches:

 int f1(int);  // (1)  int f1(double);  // (2)  f1(4);  // calls (1): perfect match   //           ((2) requires a standard conversion)  int f2(int);  // (3)  int f2(char);  // (4)  f2(true);  // calls (3): match with promotion   //           ((4) requires stronger standard conversion)  class X {    public:      X(int);  };  int f3(X);  // (5)  int f3(...);  // (6)  f3(7);  // calls (5): match with user-defined conversion   //           ((6) requires a match with ellipsis)  

Note that overload resolution occurs after template argument deduction, and this deduction does not consider all these sorts of conversions. The following example illustrates this:

 template <typename T>  class MyString {    public:      MyString(T const*);  // converting constructor   ...  };  template<typename T>  MyString<T> truncate(MyString<T> const&, int);  int main()  {      MyString<char> str1, str2;      str1 = truncate<char>("Hello World", 5);  // OK  str2 = truncate("Hello World", 5);  // ERROR  } 

The implicit conversion provided through the converting constructor is not considered during template argument deduction. The initialization of str2 finds no viable function truncate() ; hence overload resolution is not performed at all.

The previous principles are only a first approximation, but they cover many cases. Yet there are quite a few common situations that are not adequately explained by these rules. We proceed with a brief discussion of the most important refinements of these rules.

B.2.1 The Implied Argument for Member Functions

Calls to nonstatic member functions have a hidden parameter that is accessible in the definition of the member function as *this . For a member function of a class MyClass , the hidden parameter is usually of type MyClass& (for non- const member functions) or MyClass const& (for const member functions). [1] This is somewhat surprising given that this has a pointer type. It would have been nicer to make this equivalent to what is now *this . However, this was part of an early version of C++ before reference types were part of the language, and by the time reference types were added, too much code already depended on this being a pointer.

[1] It could also be of type MyClass volatile& or MyClass const volatile& if the member function was volatile , but this is extremely rare.

The hidden *this parameter participates in overload resolution just like the explicit parameters. Most of the time this is quite natural, but occasionally it comes unexpectedly. The following example shows a string-like class that does not work as intended (yet we have seen such code in the real world):

 #include <stddef.h>  class BadString {    public:      BadString(char const*);  ...   // character access through subscripting:  char& operator[] (size_t);  // (1)  char const& operator[] (size_t) const;  // implicit conversion to null-terminated byte string:  operator char* ();  // (2)  operator char const* ();  ...  };  int main()  {      BadString str("correkt");      str[5] = 'c';  // possibly an overload resolution ambiguity!  } 

At first, nothing seems ambiguous about the expression str[5] . The subscript operator at (1) seems like a perfect match. However, it is not quite perfect because the argument 5 has type int , and the operator expects an unsigned integer type ( size_t and std::size_t usually have type unsigned int or unsigned long , but never type int ). Still, a simple standard integer conversion makes (1) easily viable. However, there is another viable candidate: the built-in subscript operator. Indeed, if we apply the implicit conversion operator to str (which is the implicit member function argument), we obtain a pointer type, and now the built-in subscript operator applies. This built-in operator takes an argument of type ptrdiff_t , which on many platforms is equivalent to int and therefore is a perfect match for the argument 5 . So even though the built-in subscript operator is a poor match (by user-defined conversion) for the implied argument, it is a better match than the operator defined at (1) for the actual subscript! Hence the potential ambiguity. [2] To solve this kind of problem portably, you can declare operator [] with a ptrdiff_t parameter, or you can replace the implicit type conversion to char* by an explicit conversion (which is usually recommended anyway).

[2] Note that the ambiguity exists only on platforms for which size_t is a synonym for unsigned int .On platforms for which it is a synonym for unsigned long , the type ptrdiff_t is a typedef of long , and no ambiguity exists because the built-in subscript operator also requires a conversion of the subscript expression.

It is possible for a set of viable candidates to contain both static and nonstatic members . When comparing a static member with a nonstatic member, the quality of the match of the implicit argument is ignored (only the nonstatic member has an implicit *this argument).

B.2.2 Refining the Perfect Match

For an argument of type int , there are three common parameter types that constitute a perfect match: int , int& , and int const& . However, it is rather common to overload a function on both kinds of references:

 void report(int&);  // (1)  void report(int const&);  // (2)  int main()  {      for (int k = 0; k<10; ++k) {          report(k);  // calls (1)  }      report(42);  // calls (2)  } 

In such cases the version without the extra const is preferred for lvalues, whereas the version with const is preferred for rvalues.

Note that this also applies to the implicit argument of a member function call:

 class Wonder {    public:      void tick();  // (1)  void tick() const;  // (2)  void tack() const;  // (3)  };  void run(Wonder& device)  {      device.tick();  // calls (1)  device.tack();  // calls (3) because there is no non-  const  version   // of  Wonder::tack()  } 

Finally, the following modification of our earlier example illustrates that two perfect matches can also create an ambiguity if you overload with and without references:

 void report(int);  // (1)  void report(int&);  // (2)  void report(int const&);  // (3)  int main()  {      for (int k = 0; k<10; ++k) {          report(k);  // ambiguous: (1) and (2) match equally well  }      report(42);  // ambiguous: (1) and (3) match equally well  } 

To summarize:

  • T and T const& both match equally well for an rvalue of type T .

  • T and T& both match equally well for an lvalue of type T .

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net