У меня есть этот код
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
,
Это языковая причуда. Первый 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&)
все еще жизнеспособен.
Других решений пока нет …