C++
Разрешение перегрузки
Поиск…
замечания
Разрешение перегрузки происходит в нескольких разных ситуациях
- Вызовы для названных перегруженных функций. Кандидатами являются все функции, найденные при поиске по имени.
- Вызывает объект класса. Кандидатами обычно являются все перегруженные операторы вызова функции класса.
- Использование оператора. Кандидатами являются перегруженные функции оператора в области пространства имен, перегруженные функции оператора в левом классе (если есть) и встроенные операторы.
- Разрешение перегрузки, чтобы найти правильную функцию оператора преобразования или конструктор для вызова для инициализации
- Для прямой инициализации без списка (
Class c(value)
) кандидаты являются конструкторамиClass
. - Для инициализации копии без списка (
Class c = value
) и для поиска функции, определяемой пользователем, для вызова в пользовательской последовательности преобразования. Кандидаты являются конструкторамиClass
и если источником является объект класса, его операторы преобразования. - Для инициализации
Nonclass c = classObject
из объекта класса (Nonclass c = classObject
). Кандидаты - это функции оператора преобразования объекта инициализации. - Для инициализации ссылки с объектом класса (
R &r = classObject
), когда класс имеет функции оператора преобразования, которые дают значения, которые могут быть связаны непосредственно сr
. Кандидатами являются такие функции оператора преобразования. - Для инициализации списка неагрегатного объекта класса (
Class c{1, 2, 3}
) кандидаты являются конструкторами списка инициализаторов для первого прохода через разрешение перегрузки. Если это не находит жизнеспособного кандидата, выполняется второй проход через разрешение перегрузки с конструкторамиClass
качестве кандидатов.
- Для прямой инициализации без списка (
Полное совпадение
Перегрузка без конверсий, необходимых для типов параметров, или только преобразования, необходимые между типами, которые все еще считаются точными совпадениями, предпочтительнее перегрузки, которая требует других преобразований для вызова.
void f(int x);
void f(double x);
f(42); // calls f(int)
Когда аргумент связывается с ссылкой на тот же тип, считается, что совпадение не требует преобразования, даже если ссылка больше 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
Для целей разрешения перегрузки тип «массив T
» считается точно совпадающим с типом «указатель на T
», и тип функции T
считается точно совпадающим с типом указателя функции T*
, хотя оба требуют преобразования.
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
Категоризация аргумента по стоимости параметра
Разрешение перегрузки разделяет стоимость передачи аргумента параметру в одну из четырех различных категорий, называемых «последовательностями». Каждая последовательность может включать в себя нуль, одно или несколько преобразований
Стандартная последовательность преобразования
void f(int a); f(42);
Определенная пользователем последовательность преобразования
void f(std::string s); f("hello");
Последовательность преобразования эллипсиса
void f(...); f(42);
Последовательность инициализации списка
void f(std::vector<int> v); f({1, 2, 3});
Общий принцип заключается в том, что стандартные конверсионные последовательности являются самыми дешевыми, за ними следуют пользовательские последовательности преобразования, за которыми следуют последовательности преобразования многоточия.
Частным случаем является последовательность инициализации списка, которая не является преобразованием (список инициализаторов не является выражением с типом). Его стоимость определяется путем определения ее эквивалентности одной из трех других последовательностей преобразования в зависимости от типа параметра и формы списка инициализаторов.
Проверка имени и проверка доступа
Разрешение перегрузки происходит после поиска имени. Это означает, что функция лучшего соответствия не будет выбрана с помощью разрешения перегрузки, если она потеряет поиск по имени:
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
};
Разрешение перегрузки происходит перед проверкой доступа. Недопустимая функция может быть выбрана с помощью разрешения перегрузки, если она лучше соответствует доступной функции.
class C {
public:
static void f(double x);
private:
static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.
Аналогично, разрешение перегрузки происходит без проверки того, правильно ли сформирован результирующий вызов в отношении explicit
:
struct X {
explicit X(int );
X(char );
};
void foo(X );
foo({4}); // X(int) is better much, but expression is
// ill-formed because selected constructor is explicit
Перегрузка по ссылке
Вы должны быть очень осторожны при предоставлении ссылочной экспедиторской перегрузки, как это может соответствовать слишком хорошо:
struct A {
A() = default; // #1
A(A const& ) = default; // #2
template <class T>
A(T&& ); // #3
};
Цель заключалась в том, что A
является скопируемым и что у нас есть этот другой конструктор, который может инициализировать какой-либо другой элемент. Тем не мение:
A a; // calls #1
A b(a); // calls #3!
Для вызова строительства есть два жизнеспособных матча:
A(A const& ); // #2
A(A& ); // #3, with T = A&
Оба являются точными совпадениями, но #3
ссылается на менее cv -qualified объект, чем #2
, поэтому он имеет лучшую стандартную последовательность преобразования и является наилучшей жизнеспособной функцией.
Решение здесь состоит в том, чтобы всегда ограничивать эти конструкторы (например, используя SFINAE):
template <class T,
class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
>
A(T&& );
Типичная черта здесь заключается в том, чтобы исключить любой A
или класс, публично и однозначно полученный из A
из рассмотрения, что сделало бы этот конструктор плохо сформированным в примере, описанном ранее (и, следовательно, удаляемом из набора перегрузки). В результате вызывается конструктор копирования - это то, что мы хотели.
Шаги разрешения перегрузки
Шаги разрешения перегрузки:
Найти функции кандидата с помощью поиска по имени. Неквалифицированные вызовы будут выполнять как регулярный неквалифицированный поиск, так и зависящий от аргументов поиск (если применимо).
Отфильтруйте набор функций-кандидатов до набора жизнеспособных функций. Жизнеспособная функция, для которой существует неявная последовательность преобразований между аргументами, вызываемыми функцией, и параметрами, которые выполняет функция.
void f(char); // (1) void f(int ) = delete; // (2) void f(); // (3) void f(int& ); // (4) f(4); // 1,2 are viable (even though 2 is deleted!) // 3 is not viable because the argument lists don't match // 4 is not viable because we cannot bind a temporary to // a non-const lvalue reference
Выберите лучшего жизнеспособного кандидата. Жизнеспособная функция
F1
является лучшей функцией, чем другая жизнеспособная функцияF2
если неявная последовательность преобразований для каждого аргумента вF1
не хуже соответствующей имплицитной последовательности преобразований вF2
и ...:3.1. Для некоторого аргумента неявная последовательность преобразования для этого аргумента в
F1
является лучшей последовательностью преобразования, чем для этого аргумента вF2
, илиvoid f(int ); // (1) void f(char ); // (2) f(4); // call (1), better conversion sequence
3.2. В пользовательском преобразовании стандартная последовательность преобразования от возврата
F1
к типу назначения является лучшей последовательностью преобразования, чем последовательность возвращаемого типаF2
, илиstruct A { operator int(); operator double(); } a; int i = a; // a.operator int() is better than a.operator double() and a conversion float f = a; // ambiguous
3.3. В прямой привязке ссылки
F1
имеет тот же тип ссылки, что иF2
, илиstruct A { operator X&(); // #1 operator X&&(); // #2 }; A a; X& lx = a; // calls #1 X&& rx = a; // calls #2
3.4.
F1
не является специализированным шаблоном, ноF2
есть илиtemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3.5.
F1
иF2
являются специализациями шаблонов функций, ноF1
более специализирован, чемF2
.template <class T> void f(T ); // #1 template <class T> void f(T* ); // #2 int* p; f(p); // calls #2, more specialized
Заказ здесь значителен. Лучшая проверка последовательности преобразований происходит до проверки шаблона и без шаблона. Это приводит к общей ошибке при перегрузке по ссылке пересылки:
struct A {
A(A const& ); // #1
template <class T>
A(T&& ); // #2, not constrained
};
A a;
A b(a); // calls #2!
// #1 is not a template but #2 resolves to
// A(A& ), which is a less cv-qualified reference than #1
// which makes it a better implicit conversion sequence
Если в конце нет единственного лучшего жизнеспособного кандидата, вызов неоднозначен:
void f(double ) { }
void f(float ) { }
f(42); // error: ambiguous
Арифметические акции и конверсии
Преобразование целочисленного типа в соответствующий продвинутый тип лучше, чем преобразование его в какой-либо другой целочисленный тип.
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
Продвижение float
в double
лучше, чем преобразование его в другой тип с плавающей точкой.
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
Арифметические преобразования, кроме рекламных акций, не лучше и хуже друг друга.
void f(float x);
void f(long double x);
f(3.14); // ambiguous
void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous
Поэтому для обеспечения отсутствия двусмысленности при вызове функции f
с аргументами целочисленной или с плавающей запятой любого стандартного типа требуется всего восемь перегрузок, так что для каждого возможного типа аргумента либо перегрузка соответствует точно или будет выбрана уникальная перегрузка с указанным типом аргумента.
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);
Перегрузка в иерархии классов
В следующих примерах будет использоваться эта иерархия классов:
struct A { int m; };
struct B : A {};
struct C : B {};
Преобразование из типа производного класса в тип базового класса является предпочтительным для пользовательских преобразований. Это применяется при передаче по значению или по ссылке, а также при преобразовании указателя на производный в указатель на базу.
struct Unrelated {
Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)
Преобразование указателя из производного класса в базовый класс также лучше, чем преобразование в void*
.
void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)
Если в одной цепочке наследования имеется несколько перегрузок, предпочтительной является перегрузка основной производной базы. Это основано на аналогичном принципе, как виртуальная отправка: выбрана «наиболее специализированная» реализация. Тем не менее, разрешение перегрузки всегда происходит во время компиляции и никогда не будет скрытно скрываться.
void f(const A& a);
void f(const B& b);
C c;
f(c); // calls f(const B&)
B b;
A& r = b;
f(r); // calls f(const A&); the f(const B&) overload is not viable
Для указателей на члены, которые являются контравариантными относительно класса, аналогичное правило применяется в противоположном направлении: наименее производный производный класс является предпочтительным.
void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)
Перегрузка на постоянство и волатильность
Передача аргумента указателя на параметр T*
, если это возможно, лучше, чем передача его в параметр const T*
.
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
Аналогично, передача аргумента параметру T&
, если это возможно, лучше, чем передача его в параметр const T&
, даже если оба имеют точный рейтинг соответствия.
void f(int& r);
void f(const int& r);
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
Это правило применяется также к const-квалифицированным функциям-членам, где важно разрешить изменчивый доступ к неконстантным объектам и неизменный доступ к объектам const.
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
Точно так же летучая перегрузка будет менее предпочтительной, чем энергонезависимая перегрузка.
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