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.


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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.