2016-01-10 26 views
18

Mam następujący konstruktor:Dlaczego podwójne puste nawiasy klamrowe {{}} tworzą std :: initializer_list <double> z jednym elementem, a nie zerem?

MyItem(std::initializer_list<double> l) { 
    std::cout << "l size " << l.size() << ")" << std::endl; 
} 

który nazywa się później z podwójnych nawiasach klamrowych:

MyItem{{}} 

l.size wynik() daje to 1.

Co znajduje się za mechanika takie zachowanie?

Wygląda na to, że zagnieżdżony {} odgrywa rolę domyślnego konstruktora dla jedynego elementu, ale nie do końca rozumiem, dlaczego i jak działa dedukcja.

+1

Weź pod uwagę, że istnieje rosyjski stackoverflow na http://ru.stackoverflow.com –

Odpowiedz

10

Podczas korzystania z nawiasów klamrowych (inicjowanie listy) w celu zainicjowania obiektu MyItem, konstruktor listy, który pokazałeś, jest bardzo chciwy.

Te przejdzie pustej listy:

MyItem foo({}); 
MyItem foo{std::initializer_list<double>{}}; 

Przechodzi listę zawierającą jeden element - wartość zainicjowany double (0.0):

MyItem foo{{}}; 

działa, ponieważ istnieje pewne konteksty gdzie można po prostu użyć nawiasu klamrowego zamiast znanego typu. Tutaj, od preferowania konstruktora list, wie, że podana lista powinna zawierać double.

Dla kompletności wygląda na to, że przekazuje pustą listę, ale faktycznie wartość-inicjuje foo, jeśli ma domyślny konstruktor (lub w szczególnych przypadkach robi coś prawie równoważnego). Jeśli nie ma domyślnego konstruktora, wybrałby konstruktor listy, as shown here.

MyItem foo{}; 
+0

Czy możesz dowiedzieć się, jak dokładnie wnioskuje typ? Czy {} odgrywa rolę domyślnego konstruktora? Nie podano typu ani 'auto'. Interesuje mnie mechanika, na przykład, jakie kroki zajmuje kompilator, aby ją wydedukować. –

+1

@NikolayPolivanov, To może być najbardziej trudny kontekst, w którym nawiasy są dozwolone w ten sposób. Zasadniczo są one dozwolone, gdy powtarzasz typ, a kompilator już to zna. Na przykład masz 'void foo (string)'. Powiedzmy, że chcesz przekazać pierwsze n znaków z "abcde", gdzie n jest zmienną. Normalnie robisz coś takiego jak 'foo (string (" abcde ", n));'. Jest to oczywiście ciąg, więc po co powtarzać ten typ? 'foo ({" abcde ", n});' Kiedy używasz '{}', zwykle jest to inicjalizacja wartości, która zwykle wynosi 0 lub domyślny konstruktor. 'foo ({});' przekazuje pusty ciąg znaków. – chris

+1

Nawet wtedy konstruktor listy jest bardzo chciwy. Na przykład 'std :: string' ma konstruktor listy z listą' char'. Jeśli zrobisz 'foo ({5, 'A'});', możesz oczekiwać, że otrzymasz ciąg znaków składający się z pięciu znaków A (AAAAA), ponieważ istnieje do tego konstruktor. Jednakże, mimo że 5 nie jest "char", to nadal można go zamienić na 1, co oznacza, że ​​otrzymujesz postać o wartości 5 (prawdopodobnie postać kontrolna), po której następuje pojedynczy A. To właśnie są chciwi. Wywołanie '(const char *, size_t)' działa, ponieważ '" abcde "' nie może być zamienione na 'char'. – chris

5

Wyrażenie

MyItem{{}} 

oznacza wyraźny konwersji typu (oznaczenie funkcjonalne).

Według C++ Standard (5.2.3 Wyraźne konwersji typu (zapis funkcjonalne))

  1. Podobnie proste typu specyfikator lub TypeName-specyfikator następnie spreparowana lista-inicjuje utworzenie tymczasowego obiektu określonego typu, zainicjowanego na liście bezpośredniej (8.5.4), z podaną listą z rozszerzonymi zmianami, , a jego wartością jest ten tymczasowy obiekt jako wartość prava.

Klasa MyItem ma konwersja initializer-list konstruktora

MyItem(std::initializer_list<double> l) { 
    std::cout << "l size " << l.size() << ")" << std::endl; 
} 

który jest wybrany do wyraźnego konwersji typu.W rzeczywistości jest to równoważne wywołaniu

MyItem({{}}); 

Więc konstruktor pobiera listę initializer z jednego elementu

{ {} } 

skalarnym obiektu typu double mogą być inicjowane z pustym szelki {}.

Wynikiem tego wyniku jest utworzenie tymczasowego obiektu typu MyItem, który jest inicjowany przez listę inicjalizatorów zawierającą jeden element typu double, który jest inicjowany wartościami za pomocą pustych nawiasów klamrowych.

Powiązane problemy