Dlaczego {} jako argument funkcji nie prowadzi do niejednoznaczności?

20

Rozważ ten kod:

#include <vector>
#include <iostream>

enum class A
{
  X, Y
};

struct Test
{
  Test(const std::vector<double>&, const std::vector<int>& = {}, A = A::X)
  { std::cout << "vector overload" << std::endl; }

  Test(const std::vector<double>&, int, A = A::X)
  { std::cout << "int overload" << std::endl; }
};

int main()
{
  std::vector<double> v;
  Test t1(v);
  Test t2(v, {}, A::X);
}

https://godbolt.org/z/Gc_w8i

To drukuje:

vector overload
int overload

Dlaczego nie powoduje to błędu kompilacji z powodu niejednoznacznej rozdzielczości przeciążenia? Jeśli drugi konstruktor zostanie usunięty, otrzymamy vector overloaddwa razy. Jak / według jakiej miary jest intjednoznacznie lepsze dopasowanie {}niżstd::vector<int> ?

Podpis konstruktora można z pewnością jeszcze bardziej przyciąć, ale właśnie zostałem oszukany przez równoważny fragment kodu i chcę się upewnić, że nic ważnego nie zostanie utracone w tym pytaniu.

Max Langhof
źródło
Jeśli dobrze pamiętam {}jako blok kodu, przypisuję 0 do zmiennych - przykład: const char x = {}; jest ustawiony na 0 (null char), to samo dla int itp.
Seti
2
@ Seti To właśnie {}skutecznie działa w niektórych szczególnych przypadkach, ale ogólnie nie jest poprawne (na początek, std::vector<int> x = {};działa, std::vector <int> x = 0;nie działa). To nie jest tak proste jak „ {}przypisuje zero”.
Max Langhof,
Racja, to nie jest takie proste, ale wciąż przypisuje zero - myślę, że myślę, że to zachowanie jest dość mylące i nie powinno być tak naprawdę używane
Seti
2
@ Seti struct A { int x = 5; }; A a = {};nie przypisuje zera w żadnym sensie, konstruuje Az a.x = 5. Inaczej jest w przypadku A a = { 0 };inicjalizacji a.xna zero. Zero nie jest nieodłączne {}, jest nieodłączne od tego, jak każdy typ jest konstruowany domyślnie lub inicjowany na wartości. Zobacz tutaj , tutaj i tutaj .
Max Langhof
Nadal uważam, że domyślnie skonstruowana wartość jest myląca (wymaga sprawdzania zachowania lub zachowania dużej wiedzy przez cały czas)
Seti

Odpowiedzi:

12

Jest na [over.ics.list] , moje wyróżnienie

6 W przeciwnym razie, jeśli parametrem jest nie agregująca klasa X i rozdzielczość przeciążenia na [over.match.list] wybiera jeden najlepszy konstruktor C z X, aby wykonać inicjalizację obiektu typu X z listy inicjalizującej argument:

  • Jeśli C nie jest konstruktorem listy inicjalizującej, a lista inicjalizacyjna ma pojedynczy element typu cv U, gdzie U oznacza X lub klasę pochodną od X, niejawna sekwencja konwersji ma stopień dopasowania dokładnego, jeśli U wynosi X, lub stopień konwersji, jeśli U jest pochodną X.

  • W przeciwnym razie niejawna sekwencja konwersji jest zdefiniowaną przez użytkownika sekwencją konwersji, a druga standardowa sekwencja konwersji to konwersja tożsamości.

9 W przeciwnym razie, jeśli typ parametru nie jest klasą:

  • [...]

  • jeśli lista inicjalizacyjna nie ma elementów, domyślną sekwencją konwersji jest konwersja tożsamości. [Przykład:

    void f(int);
    f( { } ); // OK: identity conversion

    przykład końca]

std::vectorJest inicjowany przez konstruktora i kulą w śmiałych uzna to za określony użytkownik converison. Tymczasem, na przykład int, jest to konwersja tożsamości, więc przewyższa pozycję pierwszego c'tora.

StoryTeller - Unslander Monica
źródło
Tak, wydaje się dokładne.
Columbo
Ciekawe, że sytuacja ta jest wyraźnie uwzględniona w standardzie. Naprawdę spodziewałbym się, że będzie niejednoznaczny (i wygląda na to, że można to łatwo określić w ten sposób). Nie mogę podążać za rozumowaniem zawartym w ostatnim zdaniu - 0ma typ, intale nie pisze std::vector<int>, jak to się dzieje, że „jakby” wpisuje się w „nietypowy” charakter {}?
Max Langhof
@MaxLanghof - Innym sposobem na to jest to, że w przypadku typów nieklasowych nie jest to konwersja zdefiniowana przez użytkownika przez dowolny odcinek. Zamiast tego jest to bezpośredni inicjalizator wartości domyślnej. Stąd tożsamość w tym przypadku.
StoryTeller - Unslander Monica
Ta część jest jasna. Jestem zaskoczony potrzebą konwersji zdefiniowanej przez użytkownika na std::vector<int>. Jak powiedziałeś, spodziewałem się, że „typ parametru ostatecznie decyduje, jaki jest typ argumentu”, a {}„typ” (że tak powiem) std::vector<int>nie powinien wymagać konwersji (braku tożsamości), aby zainicjować std::vector<int>. Standard oczywiście mówi, że tak, więc o to chodzi, ale nie ma to dla mnie sensu. (Pamiętaj, że nie twierdzę, że ty lub standard jesteście w błędzie, po prostu próbuję pogodzić to z moimi modelami mentalnymi.)
Max Langhof
Ok, ta edycja nie była rozdzielczością, na którą liczyłem, ale dość uczciwą. : D Dziękujemy za poświęcony czas!
Max Langhof