Неоднозначность во время инициализации
Имеются две структуры:
using std::cout;
using namespace std::string_literals;
struct MyVector {
MyVector(int size) {
cout << "MyVector(int size)\n"s;
}
};
struct Gadget {
Gadget(MyVector v) {
cout << "Gadget(MyVector v)\n"s;
}
Gadget(Gadget&& gd) {
cout << "Move\n"s;
}
};
int main() {
Gadget g({10}); // Ошибка!
return 0;
}
Ошибка связана с тем, что компилятор видит неоднозначность. Он не может определить, какой из двух конструкторов ему вызывать:
- Ожидаемый нами вызов конструктора
Gadget(MyVector)
- Через временный объект и move-конструктор: При указании фигурных скобок, создается Gadget за счет вызова
Gadget(MyVector v)
. Это происходит из-за "copy list initialization". В результате полученный объект передается в качестве аргумента целевому:Gadget g(полученный_объект);
, что и может привести к альтернативному пути инициализации через move-конструктор, а следовательно к неоднозначности.
Рассмотрим еще одну структуру:
struct MyStruct {
MyStruct(int x) {
cout << "MyStruct(int x)\n"s;
}
MyStruct(MyStruct&& obj) {
cout << "Move\n"s;
}
};
int main() {
MyStruct obj( {12} ); // Неоднозначности нет
MyStruct obj({ {12} }); // Неоднозначности нет
return 0;
}
Если в конструкторе вместо int
указать, например, тип string
, то неоднозначность возникнет только в случае двойных фигурных скобок:
int main() {
MyStruct obj( {"Hello"s} ); // Неоднозначности нет
MyStruct obj({ {"Hello"s} }); // Ошибка из-за неоднозначности!
return 0;
}
Вопросы:
- Почему при использовании
int
неоднозначности нет? Как мне казалось, фигурные скобки должны приводить к альтернативному пути инициализации, как это было в первом случае, но здесь этого не происходит. - Почему при использовании
string
неоднозначность возникает только при двойных фигурных скобок?
Я понимаю, что в самом первом случае произошло неявное преобразование, но почему именно это повлияло на поведение?
Ответы (2 шт):
Мне не понятно зачем вы используется такой синтаксис потому что при использовании Gadget g{10};
код будит работать так как вы верно отметили будит неявное приобразование типов. Если вы нехочете автоматического приаброзавания то можно использовать ключевое слово explicit
.
Если таким образом вы хочете вызвать конструктор копирования то это является бесмыслиным так как у вас происходит два действия для создания одного объекта
- создаётс объекта
- копирование объекта.
Такой синтаксис работает не стабильно так как он не имеет стандартов.
Когда мы пишем Gadget g(10)
, компилятор находит одну цепочку:
Gadget
<- MyVector
<- int
.
- Проверяем Gadget(MyVector)
- Можно ли 10 представить, как MyVector?
- Проверяем MyVector(int)
- Можно ли 10 представить int?
- Да. Это точное соответствие.
? MyVector(int) - добавлен в кандидаты
- (могу ошибаться, но кажется) Иные пути не рассматриваются, так как
уже найдено точное совпадение.
! MyVector(int) - победивший кандидат
? Gadget(MyVector) - добавлен в кандидаты
- Проверяем Gadget(Gadget&&)
- Gadget(Gadget&&) - оказалось рекурсией.
- Прерываем ветку.
! Gadget(MyVector) - победивший кандидат
Если же напиcать Gadget g({10});
, то есть уже 2 цепочки:
Gadget
<- MyVector
<- int
Gadget
<- Gadget
<- MyVector
<- int
И вот почему
- Проверяем Gadget(MyVector)
- Можно ли {10} представить, как MyVector?
- Проверяем MyVector{10}
... (см. выше)
? Gadget(MyVector) - добавлен в кандидаты
- Проверяем Gadget(Gadget&&)
- Можно ли {10} представить, как Gadget&&?
- Проверяем Gadget{10}
... (см. выше)
? Gadget(Gadget&&) - добавлен в кандидаты
!!! ambiguous
Я пропущу кейс с MyStruct(int)
. Там компилятор всегда натыкается на точное соответствие int
-> int
причём всегда по пути только стандартных преобразований
Рассмотрим
struct MyStruct {
MyStruct(string) { cout << "MyStruct(string)\n"; }
MyStruct(MyStruct&&) { cout << "MyStruct(MyStruct&&)\n"; }
};
int main() {
MyStruct obj1( "Hello"s ); // Неоднозначности нет
MyStruct obj2( {"Hello"s} ); // Неоднозначности нет
MyStruct obj3({ {"Hello"s} }); // Неоднозначность
return 0;
}
obj1 - выбран MyStruct(string)
, так как "Hello"s
точно соответствует string
.
obj2 - выбран MyStruct(string)
, так как MyStruct(string{"Hello"s})
всё еще точно соответствует сигнатуре MyStruct(string)
. Чтобы пойти по пути MyStruct(MyStruct{"Hello"s})
придётся делать дополнительное преобразование типа.
obj3 - а вот тут уже начинается неоднозначность. Может имелось в виду MyStruct(string{string{"Hello"s}})
. А может MyStruct(MyStruct{string{"Hello"s}})
. Потому что для компилятора string -> string
и MyStruct -> MyStruct
- равновесные преобразования (а точнее отсутствие каких либо преобразований).
Прошу прощение за столь вольное изложение моего видения стандарта, но я не обладаю хорошей памятью и знаниями английского, чтобы подтвердить свои аргументы конкретными пунктами из него. Буду признателен, если кто-то дополнит ответ ссылками на конкретные пункты стандарта, подтвердив или опровергнув мои заявления