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 является выражением сплайсинга, функциями-кандидатами являются объявления функций, обозначенные B; в противном случае
(начиная с C++26)
  • для B выполняется поиск как для функции-члена T. Функции-кандидаты — это объявления функций, найденные при таком поиске.
Список аргументов для целей разрешения перегрузки содержит подразумеваемый аргумент объекта типа cv T.
  • Если выражение E является первичным выражением, то
  • если E является выражением сплайсинга, функциями-кандидатами являются объявления функций, обозначенные E; в противном случае
(начиная с C++26)
  • имя ищется согласно обычным правилам для вызовов функций (что может включать поиск с учетом аргументов). В силу особенностей поиска найденные объявления функций представляют собой либо:
    • только функции, не являющиеся членами (в этом случае список аргументов для целей разрешения перегрузки в точности совпадает со списком аргументов в выражении вызова функции), либо
    • только функции-члены некоторого класса T. В этом случае, если this находится в области видимости и является указателем на T или на производный от T класс, *this используется как подразумеваемый аргумент объекта. В противном случае (если this не находится в области видимости или не указывает на T) в качестве подразумеваемого аргумента объекта используется фиктивный объект типа T, и если впоследствии разрешение перегрузки выберет нестатическую функцию-член, программа будет некорректной.

Вызов объекта класса

Если E в выражении вызова функции E(args) имеет тип класса cv T, то

  • операторы вызова функции для T находятся обычным поиском имени operator() в контексте выражения (E).operator(), и каждое найденное объявление добавляется в набор функций-кандидатов.
  • Для каждой не-explicit пользовательской функции преобразования в 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@ в контексте выражения (что может включать поиск с учетом аргументов), за исключением того, что объявления функций-членов игнорируются и не препятствуют продолжению поиска во внешней области видимости. Если оба операнда бинарного оператора или единственный операнд унарного оператора имеют тип перечисления, кандидатами, не являющимися членами, становятся только функции из набора поиска, параметр которых имеет этот тип перечисления (или является ссылкой на этот тип перечисления)
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 для каждого найденного operator<=>-кандидата — члена, не являющегося членом или встроенного — добавляется синтезированный кандидат с обратным порядком двух параметров.
  • Для x != y в набор добавляются все найденные operator==-кандидаты — члены, не являющиеся членами и встроенные, если только не существует соответствующего operator!=.
  • Для выражений операторов равенства x == y и x != y для каждого найденного operator==-кандидата — члена, не являющегося членом или встроенного — добавляется синтезированный кандидат с обратным порядком двух параметров, если только не существует соответствующего operator!=.
Во всех случаях переписанные кандидаты не рассматриваются в контексте переписанного выражения. Для всех остальных операторов набор переписанных кандидатов пуст.
(начиная с 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

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

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

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

В этом случае разрешение перегрузки в качестве последнего правила выбора предпочитает непереписанные кандидаты переписанным, а переписанные, но не синтезированные кандидаты — синтезированным переписанным кандидатам.

Такой поиск с обратным порядком аргументов позволяет определить только operator<=>(std::string,constchar*) и operator==(std::string,constchar*), чтобы получить все сравнения между std::string и constchar* в обоих направлениях. Подробнее см. сравнения по умолчанию.

(начиная с C++20)

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

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

В противном случае функциями-кандидатами являются все конвертирующие конструкторы инициализируемого класса. Список аргументов — это выражение инициализатора.

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

(начиная с C++11)

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

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

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

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

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

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

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

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

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

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

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

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

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

Подробнее см. перегрузку шаблонов функций.

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

(начиная с C++20)

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

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

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

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

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

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

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

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

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

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

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

(до C++23)

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

1) пользовательские преобразования нельзя применять к неявному параметру объекта
2) rvalue можно привязать к неконстантному неявному параметру объекта (если только речь не идет о функции-члене с 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 на неконстантный тип или аргумент 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:
template<typename T = int>
struct S
{
    constexpr void f(); // #1
    constexpr void f(this S&) requires true; // #2
};

void test()
{
    S<> s;
    s.f(); // calls #2
}
, либо, если это не так,
(начиная с 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 в 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 привязывают параметр-ссылку к чему-либо, кроме неявного параметра объекта функции-члена с квалификатором ссылки, причем 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 является собственной подмножеством cv-квалификации результата S2, а S1 не является устаревшим преобразованием строкового литерала из массива в указатель(до C++11).

(до 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*)
либо, если это не так,
f) S1 и S2 привязывают параметры-ссылки, отличающиеся только cv-квалификацией верхнего уровня, а тип S1 имеет меньшую cv-квалификацию, чем тип 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
либо, если это не так,
g) S1 и S2 привязывают один и тот же тип ссылки «ссылка на T» и имеют исходные типы V1 и V2 соответственно, причем стандартная последовательность преобразований от V1* к T* лучше стандартной последовательности преобразований от V2* к T*:
struct Z {};

struct A
{
    operator Z&();
    operator const Z&();  // overload #1
};

struct B
{
    operator Z();
    operator const Z&&(); // overload #2
};

const Z& r1 = A();        // OK, uses #1
const Z&& r2 = B();       // OK, uses #2
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 (возможно, cv-квалифицированного), последовательность неявных преобразований имеет ранг преобразования. (Обратите внимание на отличие от агрегатов: агрегаты напрямую инициализируются из одноэлементных списков инициализаторов до рассмотрения агрегатной инициализации, а неагрегаты рассматривают конструкторы 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* была предпочтительнее, чем в constchar*,
хотя первое преобразование считается устаревшим
ранг устаревшего
преобразования понижен (оно
удалено в C++11)
CWG 162 C++98 набор перегрузок, указанный в F, мог
содержать нестатическую функцию-член в случае &F(args)
в этом случае некорректность возникает, только если
разрешение перегрузки выбирает
нестатическую функцию-член
CWG 233 C++98 ссылки и указатели обрабатывались непоследовательно при
разрешении перегрузки с пользовательскими преобразованиями
они обрабатываются
последовательно
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 неявная пользовательская функция преобразования с ref-квалификатором не имела соответствующей функции-заместителя у неё есть соответствующая
функция-заместитель
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 ограничение преобразования для списков designated initializer
применялось, даже если параметр является ссылкой
в этом случае ограничение не применяется
CWG 2789 C++23 явный объектный параметр учитывался
при сравнении списков типов параметров
исключён
CWG 2856 C++11 разрешение перегрузки для инициализации по умолчанию в контексте
инициализации копированием списком учитывало только преобразующий конструктор
учитываются все конструкторы
CWG 2919 C++98 набор кандидатов для инициализации ссылки посредством преобразования
зависел от целевого типа инициализации
зависит от целевого
типа преобразования
P2468R2 C++20 переписанные кандидаты на основе operator== добавляются
для a != b, даже если существует соответствующий operator!=
не добавляются
  1. Тип первого параметра встроенного оператора присваивания — «ссылка lvalue на возможно volatile-квалифицированный T». Ссылки этого типа не могут привязываться к временному объекту.

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
    • 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/cpp/language/overload_resolution

Spec-Zone.ru

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