Spec-Zone.ru › C++

Разрешение перегрузки

Для компиляции вызова функции компилятор должен вначале выполнить поиск имени, который для функций может включать поиск с зависимостью от аргументов, а для шаблонов функций может быть дополнен выводом аргументов шаблона.

Если имя ссылается на более чем одну сущность, оно считается перегруженным, и компилятор должен определить, какой вариант перегрузки вызвать. Проще говоря, вызывается вариант перегрузки, чьи параметры наиболее точно соответствуют аргументам.

Подробно, разрешение перегрузки происходит в следующие этапы:

  1. Создание набора кандидатных функций.
  2. Уточнение набора до пригодных функций.
  3. Анализ набора для определения единственной лучшей пригодной функции (это может включать ранжирование неявных последовательностей преобразований).
void f(long);
void f(float);
 
f(0L); // calls f(long)
f(0);  // error: ambiguous overload

Помимо вызовов функций, перегруженные имена функций могут появляться в нескольких дополнительных контекстах, где применяются разные правила: см. Адрес перегруженной функции.

Если функция не может быть выбрана разрешением перегрузки, она не может быть использована (например, это шаблонная сущность с невыполненным ограничением).

Кандидатные функции

Перед началом разрешения перегрузки функции, выбранные поиском имени и выводом аргументов шаблона, объединяются, чтобы образовать набор кандидатных функций. Точные детали зависят от контекста, в котором происходит разрешение перегрузки.

Вызов именованной функции

Если E в выражении вызова функции E(args) называет набор перегруженных функций и/или шаблонов функций (но не вызываемые объекты), следуют следующие правила:

  • Если выражение 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(args) имеет тип класса 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; } // conversion function to pointer to function
    using fp2 = int(*)(float);
    operator fp2() { return f2; } // conversion function to pointer to function
} a;
 
int i = a(1); // calls f1 via pointer returned from conversion function

Вызов перегруженного оператора

Если хотя бы один из аргументов оператора в выражении имеет тип класса или тип перечисления, как встроенные операторы, так и перегрузки пользовательских операторов участвуют в разрешении перегрузки, причём набор кандидатных функций выбирается следующим образом:

Для унарного оператора @ с аргументом типа T1 (после удаления cv-квалификаций) или бинарного оператора @ с левым операндом типа T1 и правым операндом типа T2 (после удаления cv-квалификаций), готовятся следующие наборы кандидатных функций:

1) кандидаты-члены: если T1 является полным классом или классом, который в настоящее время определяется, набор кандидатов-членов является результатом квалифицированного поиска имени T1::operator@. Во всех остальных случаях набор кандидатов-членов пуст.
2) кандидаты-нечлены: Для операторов, где перегрузка операторов допускает нечленные формы, все объявления, найденные неквалифицированным поиском имени operator@ в контексте выражения (что может включать ADL), за исключением того, что объявления членских функций игнорируются и не препятствуют поиску в следующем содержащем scope. Если оба операнда бинарного оператора или единственный операнд унарного оператора имеют тип перечисления, единственными функциями из набора поиска, которые становятся кандидатами-нечленами, являются те, чьи параметры имеют этот тип перечисления (или ссылку на этот тип перечисления).
3) кандидаты-встроенные: Для operator,, унарного operator&, и operator->, набор встроенных кандидатов пуст. Для других операторов встроенными кандидатами являются те, которые перечислены на страницах встроенных операторов, при условии, что все операнды могут быть неявным образом преобразованы в их параметры. Если какой-либо встроенный кандидат имеет тот же список параметров, что и кандидат-нечлен или переписанный кандидат-нечлен(с C++20) который не является специализацией шаблона функции, он не добавляется в список встроенных кандидатов. При рассмотрении встроенных операторов присваивания, преобразования от их первых параметров ограничены: рассматриваются только стандартные последовательности преобразований.
4) переписанные кандидаты:
  • Для четырёх выражений операторов отношения x < y, x <= y, x > y, и x >= y, все найденные члены, нечлены и встроенные operator<=> добавляются в набор.
  • Для четырёх выражений операторов отношения x < y, x <= y, x > y, и x >= y а также выражения сравнения в три этапа x <=> y, для каждого найденного члена, нечлена и встроенного оператора<=> добавляется синтезированный кандидат с обратным порядком двух параметров.
  • Для x != y, все найденные члены, нечлены и встроенные операторы== добавляются в набор, если нет соответствующего оператора!=.
  • Для выражений операторов равенства x == y и x != y, для каждого найденного члена, нечлена и встроенного оператора== добавляется синтезированный кандидат с обратным порядком двух параметров, если нет соответствующего оператора!=.
Во всех случаях переписанные кандидаты не рассматриваются в контексте переписанного выражения. Для всех других операторов набор переписанных кандидатов пуст.
(с C++20)

Набор кандидатных функций, которые будут представлены для разрешения перегрузки, является объединением вышеуказанных наборов. Список аргументов для целей разрешения перегрузки состоит из операндов оператора, за исключением operator->, где второй операнд не является аргументом вызова функции (см. оператор доступа к члену).

struct A
{
    operator int();              // user-defined conversion
};
A operator+(const A&, const A&); // non-member user-defined operator
 
void m()
{
    A a, b;
    a + b; // member-candidates: none
           // non-member candidates: operator+(a, b)
           // built-in candidates: int(a) + int(b)
           // overload resolution chooses operator+(a, b)
}

Если разрешение перегрузки выбирает встроенного кандидата, последовательность пользовательского преобразования от операнда типа класса не допускает второй стандартной последовательности преобразований: пользовательская функция преобразования должна непосредственно задавать ожидаемый тип операнда:

struct Y { operator int*(); }; // Y is convertible to int*
int *a = Y() + 100.0;          // error: no operator+ between pointer and double

Для оператора, унарного оператора& и оператора->, если в наборе кандидатных функций нет жизнеспособных функций (см. ниже), то оператор переинтерпретируется как встроенный.

Если в результате разрешения перегрузки для оператора @, выбран кандидат с переписанным оператором<=>, то x @ y интерпретируется как переписанное выражение: 0 @ (y <=> x) если выбранный кандидат — это синтезированный кандидат с обратным порядком параметров, или (x <=> y) @ 0 в противном случае, используя выбранный переписанный оператор<=>.

Если в результате разрешения перегрузки для оператора @ (который является либо ==, либо !=) выбран кандидат с переписанным оператором==, то его тип возврата должен быть (возможно, с квалификаторами cv) bool, и x @ y интерпретируется как переписанное выражение: y == x или !(y == x) если выбранный кандидат — это синтезированный кандидат с обратным порядком параметров, или !(x == y) в противном случае, используя выбранный переписанный оператор==.

Разрешение перегрузки в этом случае имеет окончательный решающий фактор, отдающий предпочтение непереписанным кандидатам перед переписанными и несинтезированным переписанным кандидатам перед синтезированными переписанными кандидатами.

Этот поиск с обратным порядком аргументов позволяет написать только оператор<=>(std::string, const char*) и оператор==(std::string, const char*) для генерации всех сравнений между std::string и const char*, в обоих направлениях. Более подробную информацию см. в разделе стандартных сравнениях.

(с C++20)

Инициализация конструктором

Когда объект типа класса инициализируется напрямую или инициализируется по умолчанию вне контекста копирующей инициализации, в качестве кандидатных функций выступают все конструкторы инициализируемого класса. Список аргументов — это список выражений инициализатора.

Когда объект типа класса копируется из объекта того же или производного типа класса или инициализируется по умолчанию в контексте копирующей инициализации, в качестве кандидатных функций выступают все конвертирующие конструкторы инициализируемого класса. Список аргументов — это выражение инициализатора.

Копирующая инициализация с преобразованием

Если копирующая инициализация объекта типа класса требует вызова пользовательского преобразования для преобразования выражения-инициализатора типа cv S в тип cv T инициализируемого объекта, в качестве кандидатных функций выступают следующие:

  • все конвертирующие конструкторы T
  • функции неявного преобразования из S и его базовых классов (если они не скрыты) в T или класс, производный от T, или ссылка на такой. Если эта копирующая инициализация является частью последовательности прямой инициализации cv T (инициализирующей ссылку, которая привязана к первому параметру конструктора, принимающего ссылку на cv T), то явные функции преобразования также рассматриваются.

В любом случае, список аргументов для целей разрешения перегрузки состоит из одного аргумента, который представляет собой выражение инициализатора, который будет сравниваться с первым аргументом конструктора или с неявным объектным аргументом функции преобразования.

Инициализация некласса с преобразованием

Когда инициализация объекта неклассового типа 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 ссылка на cv2 T2
где cv2 T2 совместим по ссылкам с cv1 T.
  • Для прямой инициализации явные пользовательские функции преобразования также учитываются, если T2 является тем же типом, что и T или может быть преобразован к типу T с квалификационным преобразованием.

В любом случае, список аргументов для целей разрешения перегрузки состоит из одного аргумента, который представляет собой выражение инициализатора, который будет сравниваться с неявным объектным аргументом функции преобразования.

Инициализация списком

Когда объект неагрегатного класса типа T инициализируется списком, происходит двухэтапное разрешение перегрузки.

  • на первом этапе кандидатными функциями являются все конструкторы с инициализатором списка T, а список аргументов для целей разрешения перегрузки состоит из одного аргумента — списка инициализаторов
  • если разрешение перегрузки терпит неудачу на первом этапе, начинается второй этап, где кандидатными функциями являются все конструкторы T, а список аргументов для целей разрешения перегрузки состоит из отдельных элементов списка инициализаторов.

Если список инициализаторов пуст и T имеет конструктор по умолчанию, первый этап пропускается.

При копирующей инициализации списком, если на втором этапе выбран явный конструктор, инициализация некорректна (в отличие от всех остальных случаев копирующей инициализации, где явные конструкторы даже не рассматриваются).

Дополнительные правила для кандидатных функций-шаблонов

Если поиск имени обнаружил шаблон функции, выполняется вывод аргументов шаблона и проверка любых явных аргументов шаблона для нахождения значений аргументов шаблона (если таковые имеются) в данном случае:

  • Если оба шага успешны, аргументы шаблона используются для синтеза объявлений соответствующих специализаций шаблона функции, которые добавляются в набор кандидатов, и такие специализации обрабатываются так же, как и нешаблонные функции, за исключением случаев, указанных в правилах решающего фактора ниже.
  • Если вывод аргументов терпит неудачу или синтезированная специализация шаблона функции будет некорректной, такая функция не добавляется в набор кандидатов (см. SFINAE).

Если имя относится к одному или нескольким шаблонам функций, а также к набору перегруженных нешаблонных функций, то все эти функции и специализации, сгенерированные из шаблонов, являются кандидатами.

См. перегрузку шаблонов функций для получения дополнительной информации.

Если шаблон конструктора или шаблон функции преобразования имеет условный спецификатор explicit, который оказывается зависимым от значения, после вывода, если контекст требует кандидата, который не является явным, а сгенерированная специализация явная, она удаляется из набора кандидатов.

(с C++20)

Дополнительные правила для кандидатных конструкторов

По умолчанию заданные конструкторы перемещения и операторы присваивания перемещением, которые определены как удалённые, исключаются из набора кандидатов.

Конструктор унаследованный от типа класса C , у которого первый параметр имеет тип «ссылка на P» (включая такой конструктор, сгенерированный из шаблона), исключается из набора кандидатов при построении объекта типа D , если выполняются все следующие условия:

  • Список аргументов содержит ровно один аргумент.
  • C является ссылочно-связанным с P.
  • P является ссылочно-связанным с D.
(с C++11)

Дополнительные правила для кандидатов в члены функций

Если какая-либо функция-кандидат является членом функции (статической или нет) и не имеет явного параметра объекта(с C++23), но не является конструктором, она рассматривается как имеющая дополнительный параметр (неявный параметр объекта), представляющий объект, для которого она вызывается, и расположенный перед первым из фактических параметров.

Аналогично, объект, на котором вызывается член-функция, добавляется в список аргументов как неявный аргумент объекта.

Для членов-функций класса X, тип неявного параметра объекта зависит от квалификаторов cv и ref функции-члена, как описано в членах функций.

Функции пользовательского преобразования рассматриваются как члены неявного аргумента объекта для определения типа неявного параметра объекта.

Функции-члены, введённые с помощью using-объявления в производный класс, считаются членами производного класса для определения типа неявного параметра объекта.

Для статических функций-членов неявный параметр объекта считается совместимым с любым объектом: его тип не проверяется, и для него не пытается осуществить преобразование.

(до C++23)

В остальной части разрешения перегрузки неявный аргумент объекта не отличается от других аргументов, но к неявному параметру объекта применяются следующие специальные правила:

1) функции пользовательского преобразования не могут быть применены к неявному параметру объекта
2) значения типа rvalue могут быть связаны с не-const неявным параметром объекта (за исключением случаев, когда используется ref-квалифицированная функция-член)(с C++11) и не влияют на ранжирование неявных преобразований.
struct B { void f(int); };
struct A { operator B&(); };
 
A a;
a.B::f(1); // Error: user-defined conversions cannot be applied
           // to the implicit object parameter
static_cast<B&>(a).f(1); // OK

Функции, подходящие для использования

Исходя из набора функций-кандидатов, сконструированных, как описано выше, следующим шагом разрешения перегрузки является проверка аргументов и параметров для сокращения набора до набора подходящих функций

Для того, чтобы функция-кандидат была включена в набор подходящих функций, она должна удовлетворять следующим условиям:

1) Если количество аргументов равно M, подходящая функция-кандидат должна иметь ровно M параметров.
2) Если у функции-кандидата меньше M параметров, но есть параметр с эллипсисом, она подходит.
3) Если у функции-кандидата больше M параметров, и M+1-й параметр и все последующие параметры имеют значения по умолчанию, она подходит. В дальнейшем разрешении перегрузки список параметров обрезается до M.
4) Если функция имеет связанное ограничение, оно должно быть выполнено. (с C++20)
5) Для каждого аргумента должен быть, по крайней мере, один неявный процесс преобразования, преобразующий его в соответствующий параметр.
6) Если у любого параметра есть тип ссылки, учитывается привязка ссылки: если аргумент типа rvalue соответствует параметру типа lvalue ссылки не-const, или аргумент типа lvalue соответствует параметру типа rvalue ссылки, функция не подходит.

Функции пользовательского преобразования (как конструкторы преобразования, так и функции пользовательского преобразования) запрещены в неявном процессе преобразования, где это сделало бы возможным применение более одного пользовательского преобразования. В частности, они не рассматриваются, если целевой объект преобразования — первый параметр конструктора или неявный параметр объекта функции пользовательского преобразования, а этот конструктор/функция пользовательского преобразования является кандидатом для

  • копирования инициализации класса с помощью пользовательского преобразования,
  • инициализации типа, не являющегося классом, с помощью функции преобразования,
  • инициализации с помощью функции преобразования для прямой привязки ссылки,
  • инициализации с помощью конструктора на втором этапе (прямая инициализация) инициализации копированием класса,
  • инициализации с помощью списков инициализации, где список инициализаторов содержит ровно один элемент, который сам является списком инициализаторов, и целевой объект — первый параметр конструктора класса X, а преобразование — к X или ссылке на (возможно, с квалификаторами cv) X:
struct A { A(int); };
struct B { B(A); };
 
B b{{0}}; // list-initialization of B
 
// candidates: B(const B&), B(B&&), B(A)
// {0} -> B&& not viable: would have to call B(A)
// {0} -> const B&: not viable: would have to bind to rvalue, would have to call B(A)
// {0} -> A viable. Calls A(int): user-defined conversion to A is not banned
(с C++11)

Лучшая подходящая функция

Для каждой пары подходящих функций F1 и F2, неявные последовательности преобразований из i-го аргумента в i-й параметр ранжируются, чтобы определить, какая из них лучше (за исключением первого аргумента, неявный аргумент объекта для статических функций-членов не влияет на ранжирование).

F1 считается лучшей функцией, чем F2 , если неявные преобразования для всех аргументов F1 не хуже, чем неявные преобразования для всех аргументов F2 и

1) существует хотя бы один аргумент F1, чьё неявное преобразование лучше соответствующего неявного преобразования для этого аргумента F2
2) или, если нет, (только в контексте инициализации типа, не являющегося классом, с помощью преобразования), стандартная последовательность преобразования от возвращаемого типа F1 к инициализируемому типу лучше, чем стандартная последовательность преобразования от возвращаемого типа F2
3) или, если нет, (только в контексте инициализации с помощью функции преобразования для прямой привязки ссылки к типу функции), возвращаемый тип F1 — такой же тип ссылки (lvalue или rvalue), как и инициализируемая ссылка, а возвращаемый тип F2 — нет (с C++11)
4) или, если нет, F1 — функция без шаблонов, а F2 — специализация шаблона
5) или, если нет, F1 и F2 — обе специализации шаблонов, и F1 является более специализированной в соответствии с правилами частичного упорядочения для специализаций шаблонов
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 is A(A), the copy deduction candidate
 
A x(1, 2, 3); // uses #3, generated from a non-template constructor
 
template<class T>
A(T) -> A<T>;       // #6, less specialized than #5
 
A a (42); // uses #6 to deduce A<int> and #1 to initialize
A b = a;  // uses #5 to deduce A<int> and #2 to initialize
 
template<class T>
A(A<T>) -> A<A<T>>; // #7, as specialized as #5
A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize
(с C++17)

Эти парные сравнения применяются ко всем подходящим функциям. Если ровно одна подходящая функция лучше всех остальных, разрешение перегрузки успешно, и эта функция вызывается. В противном случае компиляция завершается ошибкой.

void Fcn(const int*, short); // overload #1
void Fcn(int*, int);         // overload #2
 
int i;
short s = 0;
 
void f() 
{
    Fcn(&i, 1L);  // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: 1L -> short and 1L -> int are equivalent
                  // calls Fcn(int*, int)
 
    Fcn(&i, 'c'); // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: 'c' -> int is better than 'c' -> short
                  // calls Fcn(int*, int)
 
    Fcn(&i, s);   // 1st argument: &i -> int* is better than &i -> const int*
                  // 2nd argument: s -> short is better than s -> int
                  // no winner, compilation error
}

Если наилучшая применимая функция разрешается в функцию, для которой найдено несколько объявлений, и если любые две из этих объявлений находятся в разных областях видимости и указывают аргумент по умолчанию, который сделал функцию применимой, программа некорректна.

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, default argument was not used for viability
    f();  // error: found default argument twice
}

Рейтинг неявных преобразований

Неявные преобразования аргументов-параметров, рассматриваемые при разрешении перегрузки, соответствуют неявным преобразованиям, используемым при копирующей инициализации (для параметров не по ссылке), за исключением того, что при рассмотрении преобразования в неявный объект-параметр или в левую часть оператора присваивания преобразования, создающие временные объекты, не рассматриваются. Когда параметр является неявным объектом-параметром статического члена функции, неявное преобразование является стандартным преобразованием, которое не лучше и не хуже любого другого стандартного преобразования.(с C++23)

Каждому типу стандартного преобразования присваивается один из трех рангов:

1) Точное соответствие: нет необходимости в преобразовании, преобразование lvalue-to-rvalue, преобразование квалификации, преобразование указателя на функцию,(с C++17) пользовательское преобразование типа класса в тот же класс
2) Преобразование: целочисленное продвижение, продвижение с плавающей точкой
3) Преобразование: целочисленное преобразование, преобразование с плавающей точкой, преобразование плавающей точки в целое число, преобразование указателей, преобразование указателей на члены, булево преобразование, пользовательское преобразование производного класса к его базовому

Ранг стандартного преобразования является худшим из рангов стандартных преобразований, которые оно содержит (может быть до трех преобразований)

Связывание параметра-ссылки непосредственно с выражением-аргументом является либо Идентичностью, либо Преобразованием производного-к-базовому:

struct Base {};
struct Derived : Base {} d;
 
int f(Base&);    // overload #1
int f(Derived&); // overload #2
 
int i = f(d); // d -> Derived& has rank Exact Match
              // d -> Base& has rank Conversion
              // calls f(Derived&)

Поскольку ранжирование последовательностей преобразований выполняется только с типами и категориями значений, битовое поле может быть связано с параметром-ссылкой для целей ранжирования, но если эта функция будет выбрана, она будет некорректной.

1) Стандартное преобразование всегда лучше, чем пользовательское преобразование или преобразование с помощью эллипсиса.
2) Пользовательское преобразование всегда лучше, чем преобразование с эллипсисом.
3) Стандартное преобразование S1 лучше, чем стандартное преобразование S2, если
a) S1 является правильной подпоследовательностью S2, исключая преобразования lvalue. Последовательность преобразования тождества считается подпоследовательностью любого не тождественного преобразования
b) Или, если нет, ранг S1 лучше, чем ранг S2
c) или, если нет, и S1 и S2 связываются с параметром-ссылкой, отличным от неявного объекта-параметра функции с квалификатором ref, и S1 связывает ссылку на rvalue с rvalue, а S2 связывает ссылку на lvalue с rvalue
int i;
int f1();
 
int g(const int&);  // overload #1
int g(const int&&); // overload #2
 
int j = g(i);    // lvalue int -> const int& is the only valid conversion
int k = g(f1()); // rvalue int -> const int&& better than rvalue int -> const int&
d) или, если нет, и S1 и S2 связываются с параметром-ссылкой, и S1 связывает ссылку на lvalue с функцией, а S2 связывает ссылку на rvalue с функцией.
int f(void(&)());  // overload #1
int f(void(&&)()); // overload #2
 
void g();
int i1 = f(g); // calls #1
e) или, если нет, и S1 и S2 связываются с параметрами-ссылками, отличающимися только квалификацией cv, и тип S1 менее квалифицирован, чем тип S2.
int f(const int &); // overload #1
int f(int &);       // overload #2 (both references)
 
int g(const int &); // overload #1
int g(int);         // overload #2
 
int i;
int j = f(i); // lvalue i -> int& is better than lvalue int -> const int&
              // calls f(int&)
int k = g(i); // lvalue i -> const int& ranks Exact Match
              // lvalue i -> rvalue int ranks Exact Match
              // ambiguous overload: compilation error
f) Или, если нет, S1 и S2 отличаются только преобразованием квалификации, и

квалификация cv результата S1 является надмножеством квалификации cv результата S2, и S1 не является устаревшим преобразованием массива-в-указатель строкового литерала.

(до C++20)

результат S1 может быть преобразован к результату S2 с помощью преобразования квалификации.

(с C++20)
int f(const int*);
int f(int*);
 
int i;
int j = f(&i); // &i -> int* is better than &i -> const int*, calls f(int*)
4) Последовательность пользовательского преобразования U1 лучше, чем последовательность пользовательского преобразования U2, если они вызывают ту же конструкцию/пользовательскую функцию преобразования или инициализируют тот же класс с помощью агрегатной инициализации, и в любом случае вторая последовательность стандартного преобразования в U1 лучше, чем вторая последовательность стандартного преобразования в U2
struct A
{
    operator short(); // user-defined conversion function
} a;
 
int f(int);   // overload #1
int f(float); // overload #2
 
int i = f(a); // A -> short, followed by short -> int (rank Promotion)
              // A -> short, followed by short -> float (rank Conversion)
              // calls f(int)
5) Последовательность инициализации списка L1 лучше, чем последовательность инициализации списка L2, если L1 инициализирует параметр std::initializer_list, в то время как L2 этого не делает.
void f1(int);                                 // #1
void f1(std::initializer_list<long>);         // #2
void g1() { f1({42}); }                       // chooses #2
 
void f2(std::pair<const char*, const char*>); // #3
void f2(std::initializer_list<std::string>);  // #4
void g2() { f2({"foo", "bar"}); }             // chooses #4
6) Последовательность инициализации списка L1 лучше, чем последовательность инициализации списка L2, если соответствующие параметры являются ссылками на массивы, и L1 преобразуется к типу "массив из N1 T", L2 преобразуется к типу "массив из N2 T", и N1 меньше N2. (с C++11)
(до C++20)
6) Последовательность инициализации списка L1 лучше, чем последовательность инициализации списка L2, если соответствующие параметры являются ссылками на массивы, и L1 и L2 преобразуются в массивы одного и того же типа элементов, и либо
  • количество элементов N1, инициализированных L1, меньше количества элементов N2, инициализированных L2, или
  • N1 равно N2 и L2 преобразуется в массив с неопределенным размером, а L1 - нет.
void f(int    (&&)[] ); // overload #1
void f(double (&&)[] ); // overload #2
void f(int    (&&)[2]); // overload #3
 
f({1});        // #1: Better than #2 due to conversion, better than #3 due to bounds
f({1.0});      // #2: double -> double is better than double -> int
f({1.0, 2.0}); // #2: double -> double is better than double -> int
f({1, 2});     // #3: -> int[2] is better than -> int[], 
               //     and int -> int is better than int -> double
(с C++20)

Если две последовательности преобразований неразличимы, потому что имеют одинаковый ранг, применяются следующие дополнительные правила:

1) Преобразование, не включающее указатель на bool или указатель на член к bool, лучше, чем то, которое это делает.
2) Преобразование, которое продвигает перечисление, базовый тип которого фиксирован к его базовому типу, лучше, чем преобразование к продвинутому базовому типу, если два типа различны.
enum num : char { one = '0' };
std::cout << num::one; // '0', not 48
(с C++11)
3) Преобразование в любом направлении между типом с плавающей точкой FP1 и типом с плавающей точкой FP2 лучше, чем преобразование в том же направлении между FP1 и арифметическим типом T3, если
  • ранг преобразования с плавающей точкой FP1 равен рангу FP2, и
    • T3 не является типом с плавающей точкой, или
    • T3 является типом с плавающей точкой, ранг которого не равен рангу FP1, или
    • подранг преобразования с плавающей точкой FP2 больше, чем подранг 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); // calls f(std::float32_t) on implementations where
              // float and std::float32_t have equal conversion ranks
int j = f(y); // error: ambiguous, no equal conversion rank
(с C++23)
4) Преобразование, которое преобразует указатель на производный к указателю на базовый, лучше, чем преобразование указателя на производный к указателю на void, и преобразование указателя на базовый к void лучше, чем преобразование указателя на производный к void.
5) Если Mid получен (прямо или косвенно) от Base, и Derived получен (прямо или косвенно) от Mid
a) Derived* до Mid* лучше, чем Derived* до Base*
b) Derived до Mid& или Mid&& лучше, чем Derived до Base& или Base&&
c) Base::* до Mid::* лучше, чем Base::* до Derived::*
d) Derived до Mid лучше, чем Derived до Base
e) Mid* к Base* лучше, чем Derived* к Base*
f) Mid к Base& или Base&& лучше, чем Derived к Base& или Base&&
g) Mid::* к Derived::* лучше, чем Base::* к Derived::*
h) Mid к Base лучше, чем Derived к Base

Неоднозначные последовательности преобразований ранжируются как пользовательские последовательности преобразований, так как несколько последовательностей преобразований для аргумента могут существовать только в том случае, если они связаны с различными пользовательскими преобразованиями:

class B;
 
class A { A (B&);};         // converting constructor
class B { operator A (); }; // user-defined conversion function
class C { C (B&); };        // converting constructor
 
void f(A) {} // overload #1
void f(C) {} // overload #2
 
B b;
f(b); // B -> A via ctor or B -> A via function (ambiguous conversion)
      // b -> C via ctor (user-defined conversion)
      // the conversions for overload #1 and for overload #2
      // are indistinguishable; compilation fails

Неявная последовательность преобразований в списке инициализации

В инициализации списком аргумент является списком-инициализатором, который не является выражением, поэтому неявная последовательность преобразования в тип параметра для целей разрешения перегрузки определяется следующими специальными правилами:

  • Если тип параметра является некоторым агрегатом 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};     // selects #1 (rvalue double -> double: identity conv)
 
void g(A);
g({"foo", "bar"}); // selects #3 (lvalue const char[4] -> std::string: user-def conv)
  • В противном случае, если тип параметра — "массив из N T" (это происходит только для ссылок на массивы), список инициализации должен содержать N или меньше элементов, и наихудшее необходимое неявное преобразование для преобразования каждого элемента списка (или пустая пара фигур {} если список короче N) в T — это используемое преобразование.
  • В противном случае, если тип параметра — "массив с неизвестной границей T" (это происходит только для ссылок на массивы), наихудшее неявное преобразование, необходимое для преобразования каждого элемента списка в T — это используемое преобразование.
(с C++20)
typedef int IA[3];
 
void h(const IA&);
void g(int (&&)[]);
 
h({1, 2, 3}); // int->int identity conversion
g({1, 2, 3}); // same as above since C++20
  • В противном случае, если тип параметра — это тип класса, не являющийся агрегатом X, разрешение перегрузки выбирает конструктор C объекта X для инициализации из списка аргументов
    • Если C не является конструктором для списка инициализации и список инициализации содержит один элемент типа X (возможно, с квалификаторами cv) или производного класса от X, неявная последовательность преобразований имеет ранг "Точное соответствие". (Обратите внимание на разницу с агрегатами: агрегаты инициализируются непосредственно из списков инициализации с одним элементом до рассмотрения инициализации агрегатов, неагрегаты рассматривают конструкторы для initializer_list до других конструкторов).
    • В противном случае, неявная последовательность преобразований — это последовательность пользовательских преобразований со второй стандартной последовательностью преобразований — тождественным преобразованием.

Если несколько конструкторов жизнеспособны, но ни один не лучше других, неявная последовательность преобразований является неоднозначной.

struct A { A(std::initializer_list<int>); };
void f(A);
 
struct B { B(int, double); };
void g(B);
 
g({'a', 'b'});    // calls g(B(int, double)), user-defined conversion
// g({1.0, 1,0}); // error: double->int is narrowing, not allowed in list-init
 
void f(B);
// f({'a', 'b'}); // f(A) and f(B) both user-defined conversions
  • В противном случае, если тип параметра — это агрегат, который может быть инициализирован из списка инициализации согласно инициализации агрегатов, неявная последовательность преобразований — это пользовательская последовательность преобразований со второй стандартной последовательностью преобразований — тождественным преобразованием.
struct A { int m1; double m2; };
 
void f(A);
f({'a', 'b'}); // calls f(A(int, double)), user-defined conversion
  • В противном случае, если параметр является ссылкой, применяются правила инициализации ссылок
struct A { int m1; double m2; };
 
void f(const A&);
f({'a', 'b'}); // temporary created, f(A(int, double)) called. User-defined conversion
  • В противном случае, если тип параметра не является классом, а список инициализации содержит один элемент, неявная последовательность преобразований — это последовательность, необходимая для преобразования элемента в тип параметра.
  • В противном случае, если тип параметра не является типом класса, и список инициализации не содержит элементов, неявная последовательность преобразований — это тождественное преобразование.

Если аргумент — это список инициализации с обозначениями, а параметр не является ссылкой, преобразование возможно только если параметр имеет тип агрегата, который может быть инициализирован из этого списка инициализации в соответствии с правилами для инициализации агрегатов, в этом случае неявная последовательность преобразований — это пользовательская последовательность преобразований, второй стандартный последовательность преобразований которой — это тождественное преобразование.

Если после разрешения перегрузки порядок объявления членов агрегата не совпадает для выбранной перегрузки, инициализация параметра будет некорректной.

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; calls #1
    f({.y = 2, .x = 1}, 0); // error: selects #1, initialization of a fails
                            // due to non-matching member order
    g({.x = 1, .y = 2});    // error: ambiguous between #3 and #4
}
(с C++20)

Отчеты о дефектах

Следующие отчеты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применимо к Поведение, опубликованное в Правильное поведение
CWG 1 C++98 поведение было неопределённым, когда выбиралась та же
функция с, возможно, различными значениями по
умолчанию (из разных областей видимости)
в этом случае программа
является некорректной
CWG 83 C++98 последовательность преобразования из строковой
литерали в char* была лучше, чем в const char*
хотя первая устарела
ранг устаревшего
преобразования понижен (он
был удалён в C++11)
CWG 162 C++98 это было неверно, если набор перегрузок, указанный F, содержит
нестатический член-функцию в случае &F(args)
только неверно, если разрешение перегрузки выбирает
нестатическую член-функцию в этом случае
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 issue 1467
не применяется
CWG 2137 C++11 конструкторы списков инициализации потерялись для конструкторов копирования,
когда X инициализировался из {X}
неагрегаты вначале рассматривают списки инициализации
CWG 2273 C++11 не было критерия для выбора между унаследованными и неунаследованными
конструкторами
предпочтение отдаётся неунаследованному конструктору
CWG 2673 C++20 встроенные кандидаты с тем же списком параметров,
что и переработанный кандидат без имени
добавлялись в список встроенных кандидатов
не добавляются
CWG 2712 C++98 когда рассматривается встроенный оператор присваивания,
первый параметр не мог быть привязан к временной
переменной, что уже невозможно[1]
удалено избыточное требование
CWG 2713 C++20 ограничение на преобразование, касающееся списков инициализации по умолчанию,
применялось даже если параметр является ссылкой
в этом случае не ограничивается
P2468R2 C++20 переработанные кандидаты, основанные на операторе==, добавляются
для a != b даже если существует соответствующий оператор!=
не добавляются
  1. Тип первого параметра встроенного оператора присваивания — это «ссылка на значение lvalue, возможно, с квалификатором volatile T». Ссылки такого типа не могут быть привязаны к временной переменной.

Ссылки

  • Стандарт 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]

См. также

  • Поиск имени
  • Поиск зависимый от аргументов
  • Вывод аргументов шаблона
  • SFINAE

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/overload_resolution

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API