Подтвердить что ты не робот

Компилятор считает, что "A (A &)" принимает значения r на несколько секунд?

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

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.

4b9b3361

Ответ 1

Это языковая причуда. Первый 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&) по-прежнему жизнеспособна.