Компилятор считает, что «A (A & amp;)» принимает значения на мгновение?

У меня есть этот код

struct A { A(); A(A&); };
struct B { B(const A&); };

void f(A);
void f(B);

int main() {
f(A());
}

К моему удивлению это терпит неудачу с GCC и Clang. Кланг говорит, например,

Compilation finished with errors:
source.cpp:8:10: error: no matching constructor for initialization of 'A'
f(A());
^~~
source.cpp:1:21: note: candidate constructor not viable: expects an l-value for 1st argument
struct A { A(); A(A&); };
^
source.cpp:1:16: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
struct A { A(); A(A&); };
^
source.cpp:4:13: note: passing argument to parameter here
void f(A);

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

int main() {
f({A()});
}

Они все называют второй f,

21

Решение

Это языковая причуда. Первый f соответствует лучше, потому что ваш A не требует преобразования для соответствия типу аргумента (A), но когда компилятор пытается выполнить вызов, тот факт, что не найден подходящий конструктор копирования, приводит к сбою вызова. Язык не позволяет принимать во внимание осуществимость фактического вызова при выполнении шага разрешения перегрузки.

Наиболее близкое соответствие стандартной цитате ISO / IEC 14882: 2011 13.3.3.1.2 Пользовательские последовательности преобразования [over.ics.user]:

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

В случае инициализации списка вам, вероятно, нужно взглянуть на:
13.3.3.1.2 Пользовательские последовательности преобразования [over.ics.user]

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

— Первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класса T и
список аргументов состоит из списка инициализаторов как единственного аргумента.

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

Поскольку разрешение перегрузки должно смотреть на жизнеспособные конструкторы в каждом случае для f(A) а также f(B) он должен отклонить последовательность попыток связать A() в A(A&) но B(const A&) все еще жизнеспособен.

17

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]