Разрешение перегрузки
Чтобы скомпилировать вызов функции, компилятор должен сначала выполнить поиск имени, который для функций может включать поиск, зависящий от аргументов, а для шаблонов функций может следовать вывод аргументов шаблона.
Если имя относится к более чем одному объект��, говорят, что оно перегружено, и компилятор должен определить, какую перегрузку вызывать. Проще говоря, вызывается перегрузка, параметры которой наиболее точно соответствуют аргументам.
Подробно, разрешение перегрузки проходит через следующие шаги:
- Создание набора функций кандидатов.
- Сокращение набора только до жизнеспособных функций.
- Анализ набора для определения единственной жизнеспособной функции (это может включать ранжирование последовательностей неявного преобразования)
void f(long); void f(float); f(0L); // вызов f(long) f(0); // ошибка: неоднозначная перегрузка
Помимо вызовов функций, имена перегруженных функций могут появляться в нескольких дополнительных контекстах, где применяются другие правила: смотрите Адрес перегруженной функции.
Если функция не может быть выбрана с помощью разрешения перегрузки, её нельзя использовать. (например, это шаблонная сущность с ошибочным ограничением ограничением)
[править] Функции кандидаты
Прежде чем начнётся разрешение перегрузки, функции, выбранные путём поиска имени и вывода аргумента шаблона, объединяются для формирования набора функций кандидатов. Точные детали зависят от контекста, в котором происходит разрешение перегрузки:
[править] Вызов именованной функции
Если E
в выражении вызова функции E(аргументы)
именует набор перегруженных функций и/или шаблонов функций (но не вызываемых объектов), соблюдаются следующие правила:
- Если выражение
E
имеет формуPA->B
илиA.B
(где A имеет классовый тип cv T), тоB
ищется как функция-элемент классаT
. Объявления функций, найденные этим поиском, являются функциями-кандидатами. Список аргументов для разрешения перегрузки имеет подразумеваемый объектный аргумент типа cv T. - Если выражение
E
является первичным выражением, имя просматривается в соответствии с обычными правилами для вызовов функций (которые могут включать ADL). Объявления функций, найденные этим поиском, (из-за того, как работает поиск):
- a) все функции, не являющиеся элементами (в этом случае список аргументов для разрешения перегрузки точно соответствует списку аргументов, используемому в выражении вызова функции)
- b) все функции-элементы некоторого класса
T
, и в этом случае, если this находится в области видимости и является указателем наT
или на производный классT
, *this используется в качестве подразумеваемого объектного аргумента. Иначе (еслиthis
не входит в область видимости или не указывает наT
), в качестве подразумеваемого объектного аргумента используется поддельный объект типаT
, и если разрешение перегрузки впоследствии выбирает нестатическую функцию-элемент, программа некорректна.
[править] Вызов объекта класса
Если E
в выражении вызова функции E(аргументы)
имеет классовый тип cv T
, то
- Операторы вызова функции класса T получаются обычным поиском имени operator() в контексте выражения
(E).operator()
, и каждое найденное объявление добавляется к набору функций-кандидатов. - Для каждой неявной определяемой пользователем функции преобразования в классе
T
или в базовом классеT
(если она не скрыта), чьи cv-квалификаторы равны или больше, чем cv-квалификаторы классаT
, и где функция преобразования преобразуется в:
- указатель на функцию
- ссылку на указатель на функцию
- ссылку на функцию
- затем суррогатная функция вызова с уникальным именем, первый параметр которой является результатом преобразования, остальные параметры это список параметров, принятый результатом преобразования, а тип возвращаемого значения это тип возвращаемого результата преобразования, добавляется в набор функций-кандидатов. Если эта суррогатная функция выбрана последующим разрешением перегрузки, то будет вызвана определяемая пользователем функция преобразования, а затем будет вызван результат преобразования.
В любом случае список аргументов для разрешения перегрузки это список аргументов выражения вызова функции, которому предшествует подразумеваемый объектный аргумент E
(при сопоставлении с суррогатной функцией определяемое пользователем преобразование автоматически преобразует подразумеваемый объектный аргумент в первый аргумент суррогатной функции).
int f1(int); int f2(float); struct A { using fp1 = int(*)(int); operator fp1() { return f1; } // функция преобразования в указатель на функцию using fp2 = int(*)(float); operator fp2() { return f2; } // функция преобразования в указатель на функцию } a; int i = a(1); // вызывает f1 через указатель, возвращённый из функции преобразования
[править] Вызов перегруженного оператора
Если хотя бы один из аргументов оператора в выражении имеет тип класса или тип перечисления, в разрешении перегрузки участвуют как встроенные операторы, так и пользовательские перегрузки операторов, при этом набор функций-кандидатов выбирается следующим образом:
Для унарного оператора @
, аргумент которого имеет тип T1
(после удаления cv-квалификации), или бинарного оператора @
, чей левый операнд имеет тип T1
, а правый операнд типа T2
(после удаления cv-квалификации), подготавливаются следующие наборы функций-кандидатов:
T1
является полным классом или классом, определяемым в настоящее время, набор кандидатов в элементы является результатом поиска квалифицированного имени T1::operator@
. Во всех остальных случаях множество кандидатов в элементы пусто.operator@
в контексте выражения (которое может включать ADL), за исключением того, что объявления функций-элементов игнорируются и не препятствуют продолжению поиска в следующей охватывающей области видимости. Если оба операнда бинарного оператора или единственный операнд унарного оператора имеют тип перечисления, единственными функциями из набора поиска, которые становятся кандидатами, не являющимися элементами, являются те, чей параметр имеет тот же тип перечисления (или ссылку на тот же тип перечисления).
4) переписанные кандидаты:
Во всех случаях переписанные кандидаты не рассматриваются в контексте переписанного выражения. Для всех остальных операторов переписанный набор кандидатов пуст.
|
(начиная с C++20) |
Набор функций-кандидатов, которые должны быть представлены для разрешения перегрузки, представляет собой объединение наборов, указанных выше. Список аргументов для разрешения перегрузки состоит из операндов оператора, за исключением operator->
, где второй операнд не является аргументом для вызова функции (смотрите оператор доступа к элементам).
struct A { operator int(); // определяемое пользователем преобразование }; A operator+(const A&, const A&); // определяемый пользователем оператор, // не являющийся элементом void m() { A a, b; a + b; // кандидаты в элементы: нет // кандидаты, не являющиеся элементами: operator+(a, b) // встроенные кандидаты: int(a) + int(b) // разрешение перегрузки выбирает operator+(a, b) }
Если разрешение перегрузки выбирает встроенный кандидат, определяемая пользователем последовательность преобразования из операнда классового типа не может иметь вторую стандартную последовательность преобразования: определяемая пользователем функция преобразования должна напрямую указывать ожидаемый тип операнда:
struct Y { operator int*(); }; // Y конвертируется в int* int *a = Y() + 100.0; // ошибка: нет operator+ между указателем и double
Для operator,, унарных operator& и operator->, если в наборе функций-кандидатов нет жизнеспособных функций (смотрите ниже), то оператор интерпретируется как встроенный.
Если переписанный кандидат operator<=> выбран разрешением перегрузки для оператора Если переписанный кандидат operator== выбран разрешением перегрузки для оператора Разрешение перегрузки в этом случае имеет окончательное разрешение конфликтов, предпочитающее непереписанные кандидаты переписанным кандидатам и предпочитающее несинтезированные переписанные кандидаты синтезированным переписанным кандидатам. Этот поиск с обратным порядком аргументов позволяет писать только operator<=>(std::string, const char*) и operator==(std::string, const char*) для создания всех сравнений между std::string и const char* в обоих направлениях. Подробнее смотрите сравнения по умолчанию. |
(начиная с C++20) |
[править] Инициализация конструктором
Когда объект классового типа инициализируется напрямую или инициализируется по умолчанию вне контекста инициализации копированием, все функции-кандидаты являются конструкторами инициализируемого класса. Список аргументов это список выражений инициализатора.
Когда объект классового типа инициализируется копированием из объекта того же или производного классового типа или инициализируется по умолчанию в контексте инициализации копированием, все функции-кандидаты являются конструкторами преобразования инициализируемого класса. Список аргументов является выражением инициализатора.
[править] Инициализация копированием преобразованием
Если для инициализации копированием объекта классового типа требуется, чтобы определяемое пользователем преобразование вызывалось для преобразования выражения инициализатора типа cv S
в тип cv T
инициализируемого объекта, следующие функции являются функциями-кандидатами:
- все конструкторы преобразования объекта
T
- неявные функции преобразования из
S
и его базовых классов (если они не скрыты) вT
или класс, производный отT
, или ссылку на них. Если эта инициализация копированием является частью последовательности прямой инициализации cvT
(инициализация ссылки для привязки к первому параметру конструктора, который принимает ссылку на cvT
), то также рассматриваются явные функции преобразования.
В любом случае список аргументов для разрешения перегрузки состоит из одного аргумента, который является выражением инициализатора, которое будет сравниваться с первым аргументом конструктора или с неявным объектным аргументом функции преобразования.
[править] Инициализация вне класса преобразованием
При инициализации объекта неклассового типа cv1 T
требуется определяемая пользователем функция преобразования для преобразования из выражения инициализатора классового типа cv S
, следующие функции являются кандидатами:
- неявные определяемые пользователем функции преобразования
S
и его базовых классов (если они не скрыты), которые создают типT
или тип, преобразуемый вT
по стандартной последовательности преобразования, или ссылка на такой тип. cv квалификаторы в возвращаемом типе игнорируются при выборе функций-кандидатов. - если это прямая инициализация, явные пользовательские функции преобразования
S
и его базовых классов (если они не скрыты), которые создают типT
или тип, преобразуемый вT
с помощью квалификационного преобразования или ссылки на такой тип также учитываются.
В любом случае список аргументов для разрешения перегрузки состоит из одного аргумента, который является выражением инициализатора, которое будет сравниваться с неявным объектным аргументом функции преобразования.
[править] Инициализация ссылки преобразованием
Во время инициализации ссылки, когда ссылка на cv1 T
привязана к результату lvalue или rvalue преобразования из выражения инициализатора из классового типа cv2 S
, следующие функции выбираются для набора кандидатов:
- неявные определяемые пользователем функции преобразования
S
и его базовых классов (если они не скрыты) в тип
- (при инициализации ссылки lvalue или ссылки rvalue на функцию) ссылки lvalue на cv2
T2
- (при инициализации ссылки rvalue или ссылки lvalue на функцию) cv2
T2
или rvalue ссылки на cv2T2
- (при инициализации ссылки lvalue или ссылки rvalue на функцию) ссылки lvalue на cv2
- где cv2 T2 совместим по ссылкам с cv1 T
- для прямой инициализации также учитываются явные пользовательские функции преобразования, если T2 имеет тот же тип, что и T, или может быть преобразован в тип T квалификационным преобразованием.
В любом случае список аргументов для разрешения перегрузки состоит из одного аргумента, который является выражением инициализатора, которое будет сравниваться с неявным объектным аргументом функции преобразования.
[править] Инициализация списком
Когда объект неагрегатного типа класса T
инициализируется списком, происходит двухэтапное разрешение перегрузки.
- на этапе 1 все функции-кандидаты являются конструкторами со списком инициализаторов
T
, а список аргументов для разрешения перегрузки состоит из одного аргумента - списка инициализаторов - если разрешение перегрузки не удается на этапе 1, начинается этап 2, где все функции-кандидаты являются конструкторами класса
T
, а список аргументов для разрешения перегрузки состоит из отдельных элементов списка инициализаторов.
Если список и��ициализаторов пуст и T
имеет конструктор по умолчанию, фаза 1 пропускается.
При инициализации копированием списка, если на этапе 2 выбирается явный конструктор, инициализация ошибочна (в отличие от всех инициализаций копированием, когда явные конструкторы даже не рассматриваются).
[править] Дополнительные правила для кандидатов шаблонов функций
Если поиск имени создаёт шаблон функции, выполняется вывод аргумента шаблона и проверка любых явных аргументов шаблона, чтобы найти значения аргумента шаблона (если есть), которые можно использовать в этом случае:
- если оба выполняются успешно, аргументы шаблона используются для синтеза объявлений соответствующих специализаций шаблона функции, которые добавляются к набору кандидатов, и такие специализации обрабатываются так же, как нешаблонные функции, за исключением случаев, когда в правилах разрешения конфликтов ниже указано иное;
- если вывод аргумента терпит неудачу или специализация шаблона синтезированной функции будет неправильно сформирована, такая функция не добавляется к набору кандидатов (смотрите SFINAE).
Если имя относится к одному или нескольким шаблонам функций, а также к набору перегруженных функций, не являющихся шаблонами, кандидатами являются все эти функции и специализации, сгенерированные из шаблонов.
Подробнее смотрите в разделе перегрузка шаблона функции.
Если шаблон конструктора или шаблон функции преобразования имеет условный спецификатор explicit, который после вывода оказывается ависимым от значения, если контекст требует кандидата, который не является explicit, а сгенерированная специализация является explicit, она удаляется из набора кандидатов. |
(начиная с C++20) |
[править] Дополнительные правила для кандидатов конструкторов
По умолчанию конструктор перемещения и присваивание перемещением, которые определены как удалённые, никогда не включаются в список функций-кандидатов. |
(начиная с C++11) |
Унаследованные конструкторы копирования и перемещения не включаются в список функций-кандидатов при создании объекта производного класса.
[править] Дополнительные правила для кандидатов функций-элементов
Если какая-либо функция-кандидат является функцией элементом (статической или нестатической) которая не имеет явного параметра объекта (начиная с C++23), но не является конструктором, она обрабатывается так, как если бы она имела дополнительный параметр (неявный параметр объекта), который представляет объект, для которого они вызываются, и появляется перед первым из фактические параметры.
Точно так же объект, для которого вызывается функция-элемент, добавляется к списку аргументов в качестве подразумеваемого аргумента объекта.
Для функций-элементов класса X
тип неявного параметра объекта зависит от cv-квалификаций и ссылочных-квалификаций функции-элемента, как описано в функции элементы.
Определяемые пользователем функции преобразования считаются элементами неявного объектного аргумента для целей определения типа неявного объектного параметра.
Функции-элементы, введённые using объявлением в производный класс, считаются элементами производного класса с целью определения типа неявного объектного параметра.
Для статических функций-элементов считается, что неявный объектный параметр соответствует любому объекту: его тип не проверяется, и для него не предпринимается никаких попыток преобразования. |
(до C++23) |
Для остальной части разрешения перегрузки неявный объектный аргумент неотличим от других аргументов, но к неявному объектному параметру применяются следующие специальные правила:
struct B { void f(int); }; struct A { operator B&(); }; A a; a.B::f(1); // Ошибка: пользовательские преобразования // нельзя применить к неявному параметру объекта static_cast<B&>(a).f(1); // OK
[править] Жизнеспособные функции
Учитывая набор функций-кандидатов, созданный, как описано выше, следующим шагом разрешения перегрузки является проверка аргументов и параметров, чтобы уменьшить набор до набора жизнеспособных функций
Чтобы быть включенной в набор жизнеспособных функций, функция-кандидат должна соответствовать следующим требованиям:
M
, функция-кандидат, имеющая ровно M
параметров, жизнеспособна.M
параметров, но имеет параметр с многоточием, она жизнеспособна.M
параметров, а M+1
-й параметр и все последующие параметры имеют аргументы по умолчанию, она жизнеспособна. Для остальных разрешений перегрузки список параметров усекается до M.
4) Если у функции есть связанное ограничение, оно должно быть удовлетворено
|
(начиная с C++20) |
Определённым пользователем преобразованиям (как конструкторам преобразования, так и определяемым пользователем функциям преобразования) запрещено участвовать в неявной последовательности преобразования, если это позволило бы примени��ь более одного определяемого пользователем преобразования. В частности, они не учитываются, если целью преобразования является первый параметр конструктора или неявный объектный параметр определяемой пользователем функции преобразования, и этот конструктор/определяемое пользователем преобразование является кандидатом на преобразование.
- инициализация копированием класса определённым пользователем преобразованием,
- инициализация неклассового типа функцией преобразования,
- инициализация функцией преобразования для прямой привязки ссылки,
- инициализация конструктором во время второго (прямая инициализация) шага инициализации класса копированием,
struct A { A(int); }; struct B { B(A); }; B b{{0}}; // инициализация списком B // кандидаты: B(const B&), B(B&&), B(A) // {0} -> B&& нежизнеспособно: пришлось бы вызывать B(A) // {0} -> const B&: нежизнеспособно: пришлось бы привязываться к rvalue, // пришлось бы вызывать B(A) // {0} -> Жизнеспособный. Вызовы A(int): определяемое пользователем преобразование // в A не запрещено |
(начиная с C++11) |
[править] Лучшая жизнеспособная функция
Для каждой пары жизнеспособных функций F1
и F2
последовательности неявного преобразования из i
-го аргумента в i
-й параметр оценивается, чтобы определить, какая из них лучше (за исключением первого аргумента, неявный объектный аргумент для статических функций-элементов не влияет на ранжирование)
F1
определяется как лучшая функция, чем F2
, если неявные преобразования для всех аргументов F1 не хуже, чем неявные преобразования для всех аргументов F2, и
3) или, если это не так, (только в контексте инициализации функцией преобразования для прямой привязки ссылки к типу функции), возвращаемый тип F1 является тем же ссылочным типом (lvalue или rvalue), что и инициализируемая ссылка, а возвращаемый тип F2 нет
|
(начиная с C++11) |
6) или, если это не так, F1 и F2 являются нешаблонными функциями с одинаковыми списками типов параметров, и F1 более ограничена, чем F2, в соответствии с частичным порядком ограничений
|
(начиная с C++20) |
7) или, если это не так, F1 является конструктором для класса D, F2 является конструктором для базового класса B класса D, и для всех аргументов соответствующие параметры F1 и F2 имеют один и тот же тип:
struct A { A(int = 0); }; struct B: A { using A::A; B(); }; B b; // OK, B::B() |
(начиная с C++11) |
8) или, если это не так, F2 переписанный кандидат, а F1 нет,
9) или, если это не так, F1 и F2 обе являются переписанными кандидатами, и F2 является синтезированным перезаписанным кандидатом с обратным порядком параметров, а F1 нет,
|
(начиная с C++20) |
10) или, если это не так, F1 генерируется из пользовательского руководства по выводу, а F2 не
11) или, если это не так, F1 является кандидатом на вывод копии, а F2 не нет
12) или, если это не так, F1 генерируется из нешаблонного конструктора, а F2 генерируется из шаблона конструктора:
template<class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // #5 является A(A), кандидатом на вывод копии A x (1, 2, 3); // использует #3, сгенерированный из нешаблонного конструктора template<class T> A(T) -> A<T>; // #6, менее специализирован, чем #5 A a (42); // использует #6 для вывода A<int> и #1 для инициализации A b = a; // использует #5 для вывода A<int> и #2 для инициализации template<class T> A(A<T>) -> A<A<T>>; // #7, такой же специализированный, как #5 A b2 = a; // использует #7 для вывода A<A<int>> и #1 для инициализации |
(начиная с C++17) |
Эти попарные сравнения применяются ко всем жизнеспособным функциям. Если ровно одна жизнеспособная функция лучше всех остальных, разрешение перегрузки завершается успешно и вызывается эта функция. В противном случае компиляция завершается ошибкой.
void Fcn(const int*, short); // перегрузка #1 void Fcn(int*, int); // перегрузка #2 int i; short s = 0; void f() { Fcn(&i, 1L); // 1-й аргумент: &i -> int* лучше, чем &i -> const int* // 2-й аргумент: 1L -> short и 1L -> int эквивалентны // вызывается Fcn(int*, int) Fcn(&i, 'c'); // 1-й аргумент: &i -> int* лучше, чем &i -> const int* // 2-й аргумент: 'c' -> int лучше, чем 'c' -> short // вызывается Fcn(int*, int) Fcn(&i, s); // 1-й аргумент: &i -> int* лучше, чем &i -> const int* // 2-й аргумент: s -> short лучше, чем s -> int // нет лучшего, ошибка компиляции }
Если наилучшая жизнеспособная функция разрешается в функцию, для которой было найдено несколько объявлений, и если любые два из этих объявлений находятся в разных областях видимости и указывают аргумент по умолчанию, который сделал функцию жизнеспособной, программа некорректна.
namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 5); } using A::f; using B::f; void use() { f(3); // OK, аргумент по умолчанию не использовался для жизнеспособности f(); // ошибка: аргумент по умолчанию найден дважды }
[править] Ранжирование последовательностей неявного преобразования
Последовательности неявного преобразования аргумента-параметра, рассматриваемые при разрешении перегрузки, соответствуют неявным преобразованиям, используемым при инициализации копированием (для нессылочных параметров), за исключением того, что при рассмотрении преобразования в неявный объектный параметр или в левую часть оператора присваивания, преобразования, которые создают временных объектов, не учитываются. Когда параметр является неявным параметром объекта статической функции-члена, неявная последовательность преобразования является стандартной последовательностью преобразования, которая не лучше и не хуже любой другой стандартной последовательности преобразования. (начиная с C++23)
Каждому типу стандартной последовательности преобразования присваивается один из трёх рангов:
Ранг стандартной последовательности преобразования наихудший из рангов стандартных преобразований, которые она содержит (может быть до трёх преобразований)
Привязка ссылочного параметра непосредственно к выражению аргумента это либо Идентичность, либо Преобразование производного в базовый:
struct Base {}; struct Derived : Base {} d; int f(Base&); // перегрузка #1 int f(Derived&); // перегрузка #2 int i = f(d); // d -> Derived& имеет ранг Точное Совпадение // d -> Base& имеет ранг Преобразование // вызов f(Derived&)
Поскольку ранжирование последовательностей преобразования работает только с типами и категориями значений, битовое поле может связываться со ссылочным аргументом для ранжирования, но если эта функция будет выбрана, она будет некорректна.
S1
лучше, чем стандартная последовательность преобразования S2
, еслиS1
является правильной подпоследовательностью S2
, за исключением преобразований lvalue. Последовательность преобразования идентичности считается подпоследовательностью любого нетождественного преобразованияS1
лучше, чем ранг S2
S1
, и S2
привязываются к ссылочному параметру на что-то другое, кроме неявного объектного параметра ссылочно квалифицированной функции-элемента, и S1
привязывает ссылку rvalue к rvalue, а S2
привязывает ссылку lvalue к rvalue
int i; int f1(); int g(const int&); // перегрузка #1 int g(const int&&); // перегрузка #2 int j = g(i); // lvalue int -> const int& является единственным // допустимым преобразованием int k = g(f1()); // rvalue int -> const int&& лучше, чем rvalue int -> const int&
S1
, и S2
привязываются к ссылочному параметру, а S1
привязывает ссылку lvalue к функции, тогда как S2
привязывает ссылку rvalue к функции.
int f(void(&)()); // перегрузка #1 int f(void(&&)()); // перегрузка #2 void g(); int i1 = f(g); // вызывает #1
S1
, и S2
привязываются к ссылочным параметрам, отлича��щимся только cv-квалификацией верхнего уровня, а тип S1
менее cv-квалифицирован, чем S2
.
int f(const int &); // перегрузка #1 int f(int &); // перегрузка #2 (обе ссылки) int g(const int &); // перегрузка #1 int g(int); // перегрузка #2 int i; int j = f(i); // lvalue i -> int& лучше, чем int -> const int& // вызывается f(int&) int k = g(i); // lvalue i -> const int& ранги Точно Совпадают // lvalue i -> rvalue int ранги Точно Совпадают // неоднозначная перегрузка: ошибка компиляции
cv-квалификация результата |
(до C++20) |
результат |
(начиная с C++20) |
int f(const int*); int f(int*); int i; int j = f(&i); // &i -> int* лучше, чем &i -> const int*, вызывается f(int*)
U1
лучше, чем пользовательская последовательность преобразования U2
, если они вызывают один и тот же конструктор/пользовательскую функцию преобразования или инициализируют один и тот же класс агрегатной инициализацией, и в любом случае вторая стандартная последовательность преобразования в U1
лучше, чем вторая стандартная последовательность преобразования в U2
.
struct A { operator short(); // определяемая пользователем функция преобразования } a; int f(int); // перегрузка #1 int f(float); // перегрузка #2 int i = f(a); // A -> short, а затем short -> int (ранг Продвижение) // A -> short, а затем short -> float (ранг Преобразование) // calls f(int)
L1
лучше, чем последовательность инициализации списком L2
, если L1
инициализирует параметр std::initializer_list, а L2
нет.
void f1(int); // #1 void f1(std::initializer_list<long>); // #2 void g1() { f1({42}); } // выбирает #2 void f2(std::pair<const char*, const char*>); // #3 void f2(std::initializer_list<std::string>); // #4 void g2() { f2({"foo", "bar"}); } // выбирает #4
6) Последовательность инициализации списком L1 лучше, чем последовательность инициализации списком L2 , если соответствующие параметры являются ссылками на массивы, а L1 преобразуется в тип "массив из N1 T," L2 преобразуется в тип "массив из N2 T", и N1 меньше, чем N2.
|
(начиная с C++11) (до C++20) |
6) Последовательность инициализации списком L1 лучше, чем последовательность инициализации списком L2 , если соответствующие параметры являются ссылками на массивы, а L1 и L2 преобразуются в массивы элементов одного и того же типа, и либо
void f(int (&&)[] ); // перегрузка #1 void f(double (&&)[] ); // перегрузка #2 void f(int (&&)[2]); // перегрузка #3 f({1}); // #1: Лучше, чем #2 из-за преобразования, лучше, чем #3 из-за ограничений f({1.0}); // #2: double -> double лучше, чем double -> int f({1.0, 2.0}); // #2: double -> double лучше, чем double -> int f({1, 2}); // #3: -> int[2] лучше, чем -> int[], // и int -> int лучше, чем int -> double |
(начиная с C++20) |
Если две последовательности преобразования неразличимы из-за того, что они имеют одинаковый ранг, применяются следующие дополнительные правила:
2) Преобразование, которое продвигает перечисление, базовый тип которого фиксируется к его базовому типу, лучше, чем преобразование, которое продвигает к продвинутому базовому типу, если эти два типа различны.
enum num : char { one = '0' }; std::cout << num::one; // '0', не 48 |
(начиная с C++11) |
3) Преобразование в любом направлении между типом с плавающей запятой
FP1 и типом с плавающей запятой FP2 лучше, чем преобразование в том же направл��нии между FP1 и арифметическим типом T3 , если
int f(std::float32_t); int f(std::float64_t); int f(long long); float x; std::float16_t y; int i = f(x); // вызывает f(std::float32_t) в реализациях, где float // и std::float32_t имеют одинаковые ранги преобразования int j = f(y); // ошибка: неоднозначно, нет равного ранга преобразования |
(начиная с C++23) |
Mid
происходит (прямо или косвенно) от Base
, а Derived
происходит (прямо или косвенно) от Mid
Derived*
в Mid*
лучше, чем Derived*
в Base*
Derived
в Mid&
или Mid&&
лучше, чем Derived
в Base&
или Base&&
Base::*
в Mid::*
лучше, чем Base::*
в Derived::*
Derived
в Mid
лучше, чем Derived
в Base
Mid*
в Base*
лучше, чем Derived*
в Base*
Mid
в Base&
или Base&&
лучше, чем Derived
в Base&
или Base&&
Mid::*
в Derived::*
лучше, чем Base::*
в Derived::*
Mid
в Base
лучше, чем Derived
в Base
Неоднозначные последовательности преобразования ранжируются как определяемые пользователем последовательности преобразования, поскольку несколько последовательностей преобразования для аргумента могут существовать только в том случае, если они включают разные определяемые пользователем преобразования:
class B; class A { A (B&);}; // конструктор преобразования class B { operator A (); }; // определяемая пользователем функция преобразования class C { C (B&); }; // конструктор преобразования void f(A) {} // перегрузка #1 void f(C) {} // перегрузка #2 B b; f(b); // B -> A через конструктор или B -> A через функцию (неоднозначное преобразование) // b -> C через конструктор (определяемое пользователем преобразование) // преобразования для перегрузки #1 и для перегрузки #2 // неразличимы; компиляция завершается ошибкой
[править] Неявная последовательность преобразования в инициализации списком
При инициализации списком аргумент представляет собой список-инициализации-в-фигурных-скобках, который не является выражением, поэтому неявная последовательность преобразования в тип параметра с целью разрешения перегрузки определяется следующими специальными правилами:
- Если типом параметра является некоторый агрегат
X
, а список инициализаторов состоит ровно из одного элемента того же или производного класса (возможно, cv-квалифицированного), требуется последовательность неявного преобразования для преобразования элемента в тип параметра. - Иначе, если тип параметра является ссылкой на массив символов, а в списке инициализаторов есть один элемент, являющийся строковым литералом соответствующего типа, неявная последовательность преобразования представляет собой преобразование идентичности.
- Иначе, если тип параметра std::initializer_list<X> и существует несужающее неявное преобразование каждого элемента списка инициализаторов в
X
, последовательность неявного преобразования для целей разрешения перегрузки это наихудшее необходимое преобразование. Если список-инициализации-в-фигурных-скобках пуст, последовательность преобразования это преобразование идентичности.
struct A { A(std::initializer_list<double>); // #1 A(std::initializer_list<complex<double>>); // #2 A(std::initializer_list<std::string>); // #3 }; A a{1.0, 2.0}; // выбирает #1 (rvalue double -> double: преобразование идентичности) void g(A); g({"foo", "bar"}); // выбирает #3 (lvalue const char[4] -> std::string: // пользовательское преобразование)
- Иначе, если тип параметра "массив из N T" (это происходит только для ссылок на массивы), список инициализаторов должен содержать N или меньше элементов, а наихудшее неявное преобразование необходимо для преобразования каждого элемента списка (или пустой пары фигурных скобок
{}
, если список короче N) вT
.
|
(начиная с C++20) |
typedef int IA[3]; void h(const IA&); void g(int (&&)[]); h({1, 2, 3}); // int->int преобразование идентичности g({1, 2, 3}); // то же, что и выше, начиная с C++20
- В противном случае, если тип параметра является неагрегированным типом класса
X
, разрешение перегрузки выбирает конструктор C из X для инициализации из списка инициализаторов аргументов
- Если C не является конструктором списка инициализаторов, а список инициализаторов содержит один элемент, возможно, X с cv-квалификацией, последовательность неявного преобразования имеет ранг Точного Совпадения. Если в списке инициализаторов есть один элемент, возможно cv-квалифицированного типа, производного из X, последовательность неявного преобразования имеет ранг Преобразования. (обратите внимание на отличие от агрегатов: агрегаты инициализируются непосредственно из одноэлементных списков инициализации перед рассмотрением агрегатной инициализации, неагрегаты рассматривают конструкторы списка инициализаторов перед любыми другими конструкторами)
- Иначе неявная последовательность преобразования представляет собой определяемую пользователем последовательность преобразования, а вторая стандартная последовательность преобразования представляет собой преобразование идентичности.
Если несколько конструкторов жизнеспособны, но ни один из них не лучше других, последовательность неявного преобразования является неоднозначной последовательностью преобразования.
struct A { A(std::initializer_list<int>); }; void f(A); struct B { B(int, double); }; void g(B); g({'a', 'b'}); // вызывает g(B(int, double)), определяемое пользователем преобразование // g({1.0, 1,0}); // ошибка: double->int сужение, не допускается в списке инициализации void f(B); // f({'a', 'b'}); // f(A) и f(B) это пользовательские преобразования
- Иначе, если тип параметра представляет собой совокупность, которую можно инициализировать из списка инициализаторов в соответствии с агрегатной инициализацией, неявная последовательность преобразования представляет собой определяемую пользователем последовательность преобразования, а вторая стандартная последовательность преобразования представляет собой преобразование идентичности.
struct A { int m1; double m2;}; void f(A); f({'a', 'b'}); // вызывает f(A(int, double)), определяемое пользователем преобразование
- Иначе, если параметр является ссылкой, применяются правила инициализации ссылки
struct A { int m1; double m2; }; void f(const A&); f({'a', 'b'}); // создаётся временный объект, f(A(int, double)) вызывается. // Пользовательское преобразование
- Иначе, если тип параметра не является классом, а список инициализаторов содержит один элемент, неявная последовательность преобразования это та, которая требуется для преобразования элемента в тип параметра
- Иначе, если тип параметра не является типом класса и если в списке инициализатора нет элементов, последовательность неявного преобразования является преобразованием идентичности.
Если аргумент является назначенным списком инициализаторов, преобразование возможно только в том случае, если параметр имеет агрегатный тип, который может быть инициализирован из этого списка инициализаторов в соответствии с правилами для агрегатной инициализации, и в этом случае неявная последовательность преобразования это определяемая пользователем последовательность преобразования, второй стандартной последовательностью преобразования которой является преобразование идентичности. Если после разрешения перегрузки порядок объявления элементов агрегата не соответствует выбранной перегрузке, инициализация параметра будет неправильной. struct A { int x, y; }; struct B { int y, x; }; void f(A a, int); // #1 void f(B b, ...); // #2 void g(A a); // #3 void g(B b); // #4 void h() { f({.x = 1, .y = 2}, 0); // OK; вызывается #1 f({.y = 2, .x = 1}, 0); // ошибка: выбирает #1, инициализация не удалась // из-за несоответствия порядка элементов g({.x = 1, .y = 2}); // ошибка: неоднозначность между #3 и #4 }
|
(начиная с C++20) |
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 1 | C++98 | поведение было неопределённым, когда выбрана одна и та же функция с, возможно, разными аргументами по умолчанию (из разных областей видимости) |
в этом случае программа некорректна |
CWG 83 | C++98 | последовательность преобразования из строкового литерала в char* была лучше, чем в const char*, хотя первая устарела |
понижен ранг устаревшего преобразования (удалено в C++11) |
CWG 162 | C++98 | это было недопустимо, если набор перегрузки, названный F ,содержит нестатическую функцию-элемент в случае &F(аргументы)
|
недопустимо только в том случае, если разрешение перегрузки выбирает нестатическую функцию-элемент в этом случае |
CWG 280 | C++98 | вызовы суррогатных функций не добавлялись в набор функций-кандидатов для функций преобразования, объявленных в недоступных базовых классах |
удалено ограничение доступности, программа некорректна, если выбран вызов суррогатной функции, а соответствующая функция преобразования не может быть вызвана |
CWG 415 | C++98 | когда шаблон функции выбирается в качестве кандидата, его специализации создавались с использованием вывода аргументов шаблона |
в этом случае инстанцирования не произойдет, их объявления будут синтезированы |
CWG 495 | C++98 | когда неявные преобразования для аргументов одинаково хороши, нешаблонная функция преобразования всегда была лучше, чем шаблон функции преобразования, даже если последний мог иметь лучшую стандартную последовательность преобразования |
стандартные последовательности преобразования сравниваются перед уровнями специализации |
CWG 1307 | C++11 | разрешение перегрузки на основе размера массивов не указано | более короткий массив лучше, когда это возможно |
CWG 1328 | C++11 | определение функций-кандидатов при привязке ссылки к результату преобразования было неясным |
сделано ясным |
CWG 1374 | C++98 | квалификационное преобразование проверялось перед привязкой ссылки при сравнении стандартных последовательностей преобразования |
сделано наоборот |
CWG 1385 | C++11 | неявная определяемая пользователем функция преобразования, объявленная с помощью ссылочного квалификатора, не имела соответствующей суррогатной функции |
у неё есть соответствующая суррогатная функция |
CWG 1467 | C++11 | однотипная инициализация списком агрегатов и массивов была опущена |
инициализация определена |
CWG 1601 | C++11 | преобразование из перечисления в его базовый тип не предпочитало фиксированный базовый тип |
фиксированный тип предпочтительнее того, в который он продвигается |
CWG 1608 | C++98 | набор кандидатов-элементов унарного оператора @ , аргументкоторого имеет тип T1 , был пустым, если T1 являлсяопределяемым в данный момент классом |
в этом случае набор является результатом поиска квалифицированного имени T1::operator@
|
CWG 1687 | C++98 | когда встроенный кандидат выбирается с помощью разрешения перегрузки, операнды будут подвергаться преобразованию без ограничений |
конвертировать только операнды классового типа и отключить вторую стандартную последовательность преобразования |
CWG 2052 | C++98 | неправильные специализации шаблона синтезированной функции могут быть добавлены к набору кандидатов, что сделает программу некорректной |
они не добавляются в набор кандидатов |
CWG 2076 | C++11 | определяемое пользователем преобразование применяется к одному инициализатору во вложенном списке инициализаторов во время инициализации списком из-за разрешения CWG проблема 1467 |
не применяется |
CWG 2137 | C++11 | конструкторы списка инициализаторов теряются для конструкторов копирования при инициализации списка X из {X} |
неагрегаты сначала рассматривают списки инициализаторов |
CWG 2273 | C++11 | не было разрешения конфликтов между унаследованными и неунаследованными конструкторами |
выбирается ненаследуемый конструктор |
CWG 2673 | C++20 | встроенные кандидаты с тем же списком параметров, что и переписанный кандидат, не являющийся элементом, были добавлены в список встроенных кандидатов |
не добавлены |
WG не указан | C++20 | переписанные кандидаты на основе operator== добавляются для a != b, даже если существует соответствующий operator!= |
не добавляется |
[править] Ссылки
- C++23 стандарт (ISO/IEC 14882:2023):
- 12.2 Разрешение перегрузки [over.match]
- C++20 стандарт (ISO/IEC 14882:2020):
- 12.4 Разрешение перегрузки [over.match]
- C++17 стандарт (ISO/IEC 14882:2017):
- 16.3 Разрешение перегрузки [over.match]
- C++14 стандарт (ISO/IEC 14882:2014):
- 13.3 Разрешение перегрузки [over.match]
- C++11 стандарт (ISO/IEC 14882:2011):
- 13.3 Разрешение перегрузки [over.match]
- C++03 стандарт (ISO/IEC 14882:2003):
- 13.3 Разрешение перегрузки [over.match]