Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 105: Overload resolution

Section 105.1: Categorization of argument to parameter cost

Overload resolution partitions the cost of passing an argument to a parameter into one of four di erent categorizes, called "sequences". Each sequence may include zero, one or several conversions

Standard conversion sequence

void f(int a); f(42);

User defined conversion sequence

void f(std::string s); f("hello");

Ellipsis conversion sequence

void f(...); f(42);

List initialization sequence

void f(std::vector<int> v); f({1, 2, 3});

The general principle is that Standard conversion sequences are the cheapest, followed by user defined conversion sequences, followed by ellipsis conversion sequences.

A special case is the list initialization sequence, which does not constitute a conversion (an initializer list is not an expression with a type). Its cost is determined by defining it to be equivalent to one of the other three conversion sequences, depending on the parameter type and form of initializer list.

Section 105.2: Arithmetic promotions and conversions

Converting an integer type to the corresponding promoted type is better than converting it to some other integer type.

void f(int x); void f(short x); signed char c = 42;

f(c); // calls f(int); promotion to int is better than conversion to short short s = 42;

f(s); // calls f(short); exact match is better than promotion to int

Promoting a float to double is better than converting it to some other floating point type.

void f(double x); void f(long double x);

f(3.14f); // calls f(double); promotion to double is better than conversion to long double

Arithmetic conversions other than promotions are neither better nor worse than each other.

void f(float x);

void f(long double x); f(3.14); // ambiguous

GoalKicker.com – C++ Notes for Professionals

534

void g(long x);

void g(long double x); g(42); // ambiguous g(3.14); // ambiguous

Therefore, in order to ensure that there will be no ambiguity when calling a function f with either integral or floating-point arguments of any standard type, a total of eight overloads are needed, so that for each possible argument type, either an overload matches exactly or the unique overload with the promoted argument type will be selected.

void f(int x);

void f(unsigned int x); void f(long x);

void f(unsigned long x); void f(long long x);

void f(unsigned long long x); void f(double x);

void f(long double x);

Section 105.3: Overloading on Forwarding Reference

You must be very careful when providing a forwarding reference overload as it may match too well:

struct A {

 

A() = default;

// #1

A(A const& ) = default;

// #2

template <class T>

 

A(T&& );

// #3

};

 

 

 

The intent here was that A is copyable, and that we have this other constructor that might initialize some other member. However:

A a; // calls #1

A b(a); // calls #3!

There are two viable matches for the construction call:

A(A const& ); // #2

A(A& ); // #3, with T = A&

Both are Exact Matches, but #3 takes a reference to a less cv-qualified object than #2 does, so it has the better standard conversion sequence and is the best viable function.

The solution here is to always constrain these constructors (e.g. using SFINAE):

template <class T,

class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>

>

A(T&& );

The type trait here is to exclude any A or class publicly and unambiguously derived from A from consideration, which would make this constructor ill-formed in the example described earlier (and hence removed from the overload set). As a result, the copy constructor is invoked - which is what we wanted.

GoalKicker.com – C++ Notes for Professionals

535

Section 105.4: Exact match

An overload without conversions needed for parameter types or only conversions needed between types that are still considered exact matches is preferred over an overload that requires other conversions in order to call.

void f(int x); void f(double x);

f(42); // calls f(int)

When an argument binds to a reference to the same type, the match is considered to not require a conversion even if the reference is more cv-qualified.

void f(int& x); void f(double x); int x = 42;

f(x); // argument type is int; exact match with int&

void g(const int& x); void g(int x);

g(x); // ambiguous; both overloads give exact match

For the purposes of overload resolution, the type "array of T" is considered to match exactly with the type "pointer to T", and the function type T is considered to match exactly with the function pointer type T*, even though both require conversions.

void f(int* p); void f(void* p);

void g(int* p);

void g(int (&p)[100]);

int a[100];

f(a); // calls f(int*); exact match with array-to-pointer conversion g(a); // ambiguous; both overloads give exact match

Section 105.5: Overloading on constness and volatility

Passing a pointer argument to a T* parameter, if possible, is better than passing it to a const T* parameter.

struct Base {};

struct Derived : Base {}; void f(Base* pb);

void f(const Base* pb); void f(const Derived* pd); void f(bool b);

Base b;

f(&b); // f(Base*) is better than f(const Base*) Derived d;

f(&d); // f(const Derived*) is better than f(Base*) though; // constness is only a "tie-breaker" rule

Likewise, passing an argument to a T& parameter, if possible, is better than passing it to a const T& parameter, even if both have exact match rank.

void f(int& r);

void f(const int& r);

GoalKicker.com – C++ Notes for Professionals

536

int x;

f(x); // both overloads match exactly, but f(int&) is still better const int y = 42;

f(y); // only f(const int&) is viable

This rule applies to const-qualified member functions as well, where it is important for allowing mutable access to non-const objects and immutable access to const objects.

class IntVector { public:

// ...

int* data() { return m_data; }

const int* data() const { return m_data; } private:

// ...

int* m_data;

};

IntVector v1;

int* data1 = v1.data(); // Vector::data() is better than Vector::data() const; // data1 can be used to modify the vector's data

const IntVector v2;

const int* data2 = v2.data(); // only Vector::data() const is viable;

// data2 can't be used to modify the vector's data

In the same way, a volatile overload will be less preferred than a non-volatile overload.

class AtomicInt { public:

// ...

int load();

int load() volatile; private:

// ...

};

AtomicInt a1;

a1.load(); // non-volatile overload preferred; no side effect volatile AtomicInt a2;

a2.load(); // only volatile overload is viable; side effect static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1

Section 105.6: Name lookup and access checking

Overload resolution occurs after name lookup. This means that a better-matching function will not be selected by overload resolution if it loses name lookup:

void f(int x); struct S {

void f(double x);

void g() { f(42); } // calls S::f because global f is not visible here, // even though it would be a better match

};

Overload resolution occurs before access checking. An inaccessible function might be selected by overload resolution if it is a better match than an accessible function.

class C { public:

static void f(double x);

GoalKicker.com – C++ Notes for Professionals

537