Dlaczego std :: initializer_list nie jest językiem wbudowanym?

96

Dlaczego nie std::initializer_listma wbudowanego języka podstawowego?

Wydaje mi się, że jest to dość ważna cecha C ++ 11, a mimo to nie ma własnego zarezerwowanego słowa kluczowego (lub czegoś podobnego).

Zamiast tego initializer_listjest to po prostu klasa szablonu z biblioteki standardowej, która ma specjalne, niejawne mapowanie z nowej składni listy braced-init, {...} która jest obsługiwana przez kompilator.

Na pierwszy rzut oka to rozwiązanie jest dość hakerskie .

Czy w ten sposób będą teraz wdrażane nowe dodatki do języka C ++: przez niejawne role niektórych klas szablonów, a nie przez język rdzenia ?


Rozważ te przykłady:

   widget<int> w = {1,2,3}; //this is how we want to use a class

dlaczego wybrano nową klasę:

   widget( std::initializer_list<T> init )

zamiast używać czegoś podobnego do któregokolwiek z tych pomysłów:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. klasyczną tablicę, którą prawdopodobnie mógłbyś dodać consttu i tam
  2. trzy kropki już istnieją w języku (var-args, teraz variadic templates), dlaczego nie użyć ponownie składni (i sprawić, by wyglądała jak wbudowana )
  3. tylko istniejący kontener, można dodać consti&

Wszystkie są już częścią języka. Napisałem tylko swoje 3 pierwsze pomysły, jestem pewien, że istnieje wiele innych podejść.

emesx
źródło
26
Komitet normalizacyjny nienawidzi dodawania nowych słów kluczowych!
Alex Chamberlain
11
Rozumiem, ale jest wiele możliwości rozszerzenia języka ( słowo kluczowe było tylko przykładem )
emesx
10
std::array<T>nie jest bardziej „częścią języka” niż std::initializer_list<T>. I nie są to prawie jedyne komponenty biblioteki, na których opiera się język. Zobacz new/ delete, type_inforóżne typy wyjątków size_titp.
bames53
6
@Elmes: Sugerowałbym const T(*)[N], ponieważ zachowuje się bardzo podobnie do tego, jak std::initializer_listdziała.
Mooing Duck
1
To wyjaśnia, dlaczego std::arraytablice o statycznym rozmiarze są mniej pożądanymi alternatywami.
boycy

Odpowiedzi:

48

Były już przykłady "podstawowych" funkcji języka, które zwracały typy zdefiniowane w stdprzestrzeni nazw. typeidpowraca std::type_infoi (być może rozciągając punkt) sizeofpowraca std::size_t.

W pierwszym przypadku musisz już dołączyć standardowy nagłówek, aby używać tej tak zwanej funkcji „języka podstawowego”.

Teraz w przypadku list inicjalizujących zdarza się, że do wygenerowania obiektu nie jest potrzebne słowo kluczowe, składnia to kontekstowe nawiasy klamrowe. Poza tym to to samo co type_info. Osobiście nie sądzę, że brak słowa kluczowego sprawia, że ​​jest to „bardziej hakerskie”. Być może nieco bardziej zaskakujące, ale pamiętaj, że celem było zezwolenie na taką samą składnię inicjalizatora ze wzmocnieniem, która była już dozwolona dla agregacji.

Więc tak, prawdopodobnie możesz spodziewać się więcej tej zasady projektowania w przyszłości:

  • jeśli pojawi się więcej okazji, w których możliwe jest wprowadzenie nowych funkcji bez nowych słów kluczowych, komisja je wybierze.
  • jeśli nowe funkcje wymagają złożonych typów, wówczas te typy zostaną umieszczone w stdzamiast jako wbudowane.

W związku z tym:

  • jeśli nowa funkcja wymaga złożonego typu i może zostać wprowadzona bez nowych słów kluczowych, otrzymasz to, co masz tutaj, czyli składnię „języka podstawowego” bez nowych słów kluczowych i która wykorzystuje typy bibliotek z std.

Myślę, że sprowadza się to do tego, że w C ++ nie ma absolutnego podziału między „językiem podstawowym” a standardowymi bibliotekami. W standardzie są to różne rozdziały, ale każdy z nich odnosi się do siebie nawzajem i zawsze tak było.

W C ++ 11 istnieje inne podejście, polegające na tym, że lambdy wprowadzają obiekty, które mają anonimowe typy generowane przez kompilator. Ponieważ nie mają nazw, w ogóle nie znajdują się w przestrzeni nazw, a już na pewno nie std. Nie jest to jednak odpowiednie podejście do list inicjalizujących, ponieważ używasz nazwy typu podczas pisania konstruktora, który ją akceptuje.

Steve Jessop
źródło
1
Wydaje mi się, że ten podział nie jest możliwy (mailny?) Z powodu takich ukrytych ról typów. type_infoi size_tsą fajnymi argumentami… cóż, size_tto tylko typedef… więc pomińmy to. Różnica między type_infoi initializer_listpolega na tym, że pierwszy jest wynikiem jawnego operatora, a drugi niejawnej akcji kompilatora. Wydaje mi się również, że initializer_list można by to zastąpić jakimiś już istniejącymi kontenerami ... lub jeszcze lepiej: dowolny użytkownik deklaruje jako typ argumentu!
emesx
4
... lub może to być prosty powód, że jeśli napiszesz konstruktora, vectorktóry pobiera an, arrayto możesz skonstruować wektor z dowolnej tablicy odpowiedniego typu, a nie tylko wygenerowanej przez składnię listy inicjalizacyjnej. Nie jestem pewien, czy byłoby źle konstruować kontenery z dowolnego array, ale nie jest to intencją komitetu wprowadzającego nową składnię.
Steve Jessop
2
@Christian: Nie, std::arraynie ma nawet żadnych konstruktorów. std::arraySprawa jest po prostu agregat inicjalizacja. Zapraszam również do przyłączenia się do czatu Lounge <C ++>, ponieważ ta dyskusja staje się nieco długa.
Xeo
3
@ChristianRau: Xeo oznacza, że ​​elementy są kopiowane podczas konstruowania listy inicjalizacyjnej. Kopiowanie listy inicjalizującej nie kopiuje zawartych elementów.
Mooing Duck
2
@Christian List-inicjalizacja nie implikuje initializer_list. Może to być wiele rzeczy, w tym dobra inicjalizacja bezpośrednia lub inicjalizacja zbiorcza. Żadne z nich nie obejmuje initializer_list (a niektóre po prostu nie mogą działać w ten sposób).
R. Martinho Fernandes
42

Wydaje się, że Komitet Standardowy C ++ woli nie dodawać nowych słów kluczowych, prawdopodobnie dlatego, że zwiększa to ryzyko złamania istniejącego kodu (starszy kod mógłby używać tego słowa kluczowego jako nazwy zmiennej, klasy lub czegokolwiek innego).

Ponadto wydaje mi się, że zdefiniowanie std::initializer_listkontenera z szablonem jest dość eleganckim wyborem: gdyby było to słowo kluczowe, w jaki sposób można uzyskać dostęp do jego bazowego typu? Jak byś to powtórzył? Potrzebowałbyś również kilku nowych operatorów, a to zmusiłoby cię do zapamiętania większej liczby nazw i słów kluczowych, aby zrobić to samo, co w przypadku standardowych kontenerów.

Traktowanie std::initializer_listkontenera jak każdego innego kontenera daje możliwość napisania ogólnego kodu, który działa z każdą z tych rzeczy.

AKTUALIZACJA:

Po co więc wprowadzać nowy typ, zamiast używać kombinacji istniejących? (z komentarzy)

Na początek wszystkie inne kontenery mają metody dodawania, usuwania i umieszczania elementów, które nie są pożądane w przypadku kolekcji generowanej przez kompilator. Jedynym wyjątkiem jest to std::array<>, że opakowuje tablicę o stałym rozmiarze w stylu C i dlatego pozostaje jedynym rozsądnym kandydatem.

Jednak, jak Nicol Bolas słusznie podkreśla się w komentarzach, inny, zasadnicza różnica pomiędzy std::initializer_listi wszystkie inne standardowe pojemniki (włącznie std::array<>) jest to, że te ostatnie mają semantykę wartości , natomiast std::initializer_listma semantykę referencyjnych . Kopiowanie std::initializer_list, na przykład, nie spowoduje kopię poszczególnych pierwiastków.

Co więcej (po raz kolejny dzięki uprzejmości Nicol Bolas), posiadanie specjalnego pojemnika na listy inicjalizacji nawiasów klamrowych pozwala na przeciążenie sposobu, w jaki użytkownik wykonuje inicjalizację.

Andy Prowl
źródło
4
Po co więc wprowadzać nowy typ, zamiast używać kombinacji istniejących?
emesx
3
@elmes: Właściwie to bardziej przypomina std::array. Ale std::arrayprzydziela pamięć podczas std::initializaer_listzawijania tablicy w czasie kompilacji. Potraktuj to jako różnicę między char s[] = "array";i char *s = "initializer_list";.
rodrigo
2
A to, że jest to normalny typ, sprawia, że ​​przeładowanie, specjalizacja szablonów, dekorowanie nazw i tym podobne, nie powodują problemów.
rodrigo
2
@rodrigo: std::arraynie alokuje żadnej pamięci, to zwykła T arr[N];, ta sama rzecz, która jest kopią std::initializer_list.
Xeo
6
@Xeo: T arr[N] alokuje pamięć, może nie w dynamicznej stercie, ale gdzie indziej ... Tak samo std::array. Jednak użytkownik nie initializer_listmoże skonstruować wartości niepustej, więc oczywiście nie może przydzielić pamięci.
rodrigo
6

To nic nowego. Na przykład for (i : some_container)opiera się na istnieniu określonych metod lub samodzielnych funkcji w some_containerklasie. C # jeszcze bardziej polega na swoich bibliotekach .NET. Właściwie uważam, że jest to dość eleganckie rozwiązanie, ponieważ możesz dostosować swoje zajęcia do niektórych struktur językowych bez komplikowania specyfikacji języka.

Zjawa
źródło
2
metody w klasie lub samodzielne begini endmetody. To trochę inna IMO.
emesx
3
Czy to jest? Ponownie masz konstrukcję czysto językową opartą na określonej konstrukcji twojego kodu. Można to również zrobić przez wprowadzenie nowego słowa kluczowego, na przykładiterable class MyClass { };
Spook
ale możesz umieścić metody w dowolnym miejscu, zaimplementuj je w dowolny sposób .. Jest pewne podobieństwo, zgadzam się! To pytanie dotyczy initializer_listjednak
emesx
4

To rzeczywiście nic nowego i ilu z nich zwróciło uwagę, ta praktyka istniała w C ++ i jest, powiedzmy, w C #.

Andrei Alexandrescu wspomniał jednak o dobrej uwadze w tej sprawie: możesz myśleć o niej jako o części wyimaginowanej „rdzeniowej” przestrzeni nazw, wtedy będzie to miało więcej sensu.

Tak, to rzeczywiście coś takiego: core::initializer_list, core::size_t, core::begin(), core::end()i tak dalej. To tylko niefortunny zbieg okoliczności, że stdprzestrzeń nazw zawiera w sobie pewne podstawowe konstrukcje językowe.

Artem Tokmakov
źródło
2

Nie tylko może działać całkowicie w bibliotece standardowej. Włączenie do standardowej biblioteki nie oznacza, że ​​kompilator nie potrafi płatać sprytnych sztuczek.

Chociaż może nie być w stanie tego zrobić we wszystkich przypadkach, może bardzo dobrze powiedzieć: ten typ jest dobrze znany lub typ prosty, zignorujmy initializer_listi po prostu miej w pamięci obraz tego, jaka powinna być zainicjowana wartość.

Innymi słowy int i {5};może być równoważne int i(5);lub int i=5;nawet intwrapper iw {5};Gdzie intwrapperjest prosty wrapper klasy na int z trywialny konstruktor odbywającychinitializer_list

Paul de Vrieze
źródło
Czy mamy odtwarzalne przykłady kompilatorów faktycznie wykonujących takie „sprytne sztuczki”? To trochę się rozumieć pod co-jeśli , ale chciałbym zobaczyć uzasadnienie.
underscore_d
Idea optymalizacji kompilatorów polega na tym, że kompilator może przekształcić kod na dowolny równoważny kod. W szczególności C ++ polega na optymalizacji pod kątem „darmowych” abstrakcji. Pomysł zastąpienia kodu z biblioteki standardowej jest powszechny (spójrz na listę wbudowaną gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
W rzeczywistości twój pomysł, który int i {5}obejmuje cokolwiek, std::initializer_listjest błędny. intnie ma konstruktora std::initializer_list, więc 5jest używany bezpośrednio do konstruowania. Więc główny przykład jest nieistotny; nie ma po prostu żadnej optymalizacji do wykonania. Poza tym, ponieważ std::initializer_listwiąże się to z tworzeniem przez kompilator i proxy „wyimaginowanej” tablicy, myślę, że może sprzyjać optymalizacji, ale to jest „magiczna” część kompilatora, więc jest to niezależne od tego, czy ogólnie optymalizator może zrobić coś sprytnego z ładnym nudny obiekt zawierający 2 iteratory, które wynikają
underscore_d
1

Nie jest częścią podstawowego języka, ponieważ można go zaimplementować w całości w bibliotece, po prostu w wierszach operator newi operator delete. Jakie korzyści przyniosłoby utrudnienie kompilatorom kompilacji?

Pete Becker
źródło