(Uwaga: to pytanie dotyczy braku konieczności określania liczby elementów i nadal zezwala na bezpośrednie inicjowanie typów zagnieżdżonych).
To pytanie omawia zastosowania pozostawione dla tablicy C, takiej jak int arr[20];
. W swojej odpowiedzi @James Kanze pokazuje jeden z ostatnich bastionów tablic C, jego unikalne cechy inicjalizacji:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
Nie musimy określać liczby elementów, hura! Teraz iteruj go za pomocą funkcji C ++ 11 std::begin
i std::end
from <iterator>
( lub własnych wariantów ) i nigdy nie musisz nawet myśleć o jego rozmiarze.
Czy są jakieś sposoby (prawdopodobnie TMP), aby osiągnąć to samo std::array
? Zastosowanie makr pozwoliło uatrakcyjnić wygląd. :)
??? std_array = { "here", "be", "elements" };
Edycja : wersja pośrednia, skompilowana z różnych odpowiedzi, wygląda następująco:
#include <array>
#include <utility>
template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
return { std::forward<T>(head), std::forward<Tail>(values)... };
}
// in code
auto std_array = make_array(1,2,3,4,5);
I używa wszelkiego rodzaju fajnych rzeczy C ++ 11:
- Szablony Variadic
sizeof...
- odniesienia do wartości r
- doskonała spedycja
std::array
, oczywiście- jednolita inicjalizacja
- pomijanie zwracanego typu przy jednolitej inicjalizacji
- wnioskowanie o typie (
auto
)
Przykład można znaleźć tutaj .
Jednak , jak @Johannes wskazuje w komentarzu do odpowiedzi @ Xaade, nie możesz zainicjować zagnieżdżonych typów za pomocą takiej funkcji. Przykład:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };
Ponadto liczba inicjatorów jest ograniczona do liczby argumentów funkcji i szablonów obsługiwanych przez implementację.
TMP
twojego pytania?Odpowiedzi:
Najlepsze co przychodzi mi do głowy to:
Wymaga to jednak od kompilatora wykonania NRVO, a następnie pominięcia kopii zwracanej wartości (co również jest legalne, ale nie jest wymagane). W praktyce spodziewałbym się, że każdy kompilator C ++ będzie w stanie zoptymalizować to tak, aby był tak szybki, jak bezpośrednia inicjalizacja.
źródło
make_array
połączenie.static_assert
i niektórych TMP do wykrywania, kiedyTail
nie jest niejawnie konwertowane naT
, a następnie użycieT(tail)...
, ale to pozostaje jako ćwiczenie dla czytelnika :)Spodziewałbym się prostego
make_array
.źródło
std::array<ret, sizeof...(T)>
nareturn
wyciągu. To bezcelowo wymusza istnienie konstruktora przenoszenia w typie tablicy (w przeciwieństwie do konstrukcji-z-T&&
) w C ++ 14 i C ++ 11.Łącząc kilka pomysłów z poprzednich postów, oto rozwiązanie, które działa nawet w przypadku konstrukcji zagnieżdżonych (przetestowane w GCC4.6):
O dziwo, nie można uczynić zwracanej wartości referencją rvalue, która nie działałaby w przypadku konstrukcji zagnieżdżonych. Tak czy inaczej, oto test:
(Do ostatniego wydruku używam mojej ładnej drukarki .)
Właściwie poprawmy bezpieczeństwo typowe tej konstrukcji. Zdecydowanie potrzebujemy, aby wszystkie typy były takie same. Jednym ze sposobów jest dodanie statycznej asercji, którą zredagowałem powyżej. Innym sposobem jest włączenie tylko
make_array
wtedy, gdy typy są takie same, na przykład:Tak czy inaczej, będziesz potrzebować
all_same<Args...>
cechy typu wariadycznego. Oto ona, uogólniając odstd::is_same<S, T>
(zauważ, że rozpadają ważne jest, aby umożliwić mieszanieT
,T&
,T const &
itd.):Zwróć uwagę, że
make_array()
zwraca przez copy-of-tymczasowe, które kompilator (z wystarczającymi flagami optymalizacji!) Może traktować jako wartość r lub w inny sposób optymalizować, istd::array
jest typem zagregowanym, więc kompilator może wybrać najlepszą możliwą metodę konstrukcji .Na koniec zwróć uwagę, że nie możesz uniknąć konstrukcji kopiuj / przenieś podczas
make_array
konfigurowania inicjatora. Więcstd::array<Foo,2> x{Foo(1), Foo(2)};
nie ma kopiowania / przenoszenia, aleauto x = make_array(Foo(1), Foo(2));
ma dwa kopiowanie / przenoszenie, ponieważ argumenty są przekazywanemake_array
. Nie sądzę, aby można było to poprawić, ponieważ nie można przekazać leksykalnie zmiennej listy inicjalizatora do pomocnika i wywnioskować typ i rozmiar - gdyby preprocesor miałsizeof...
funkcję dla argumentów wariadycznych, być może można to zrobić, ale nie w rdzennym języku.źródło
Używanie składni końcowego powrotu
make_array
można jeszcze bardziej uprościćNiestety w przypadku klas zagregowanych wymaga to jawnej specyfikacji typu
W rzeczywistości ta
make_array
implementacja jest wymieniona w sizeof ... operatorWersja c ++ 17
Dzięki dedukcji argumentów szablonu dla propozycji szablonów klas możemy użyć przewodników dedukcyjnych, aby pozbyć się
make_array
pomocnikaSkompilowane z
-std=c++1z
flagą pod x86-64 gcc 7.0źródło
Wiem, że minęło sporo czasu, odkąd zadano to pytanie, ale czuję, że istniejące odpowiedzi nadal mają pewne wady, dlatego chciałbym zaproponować moją nieco zmodyfikowaną wersję. Poniżej znajdują się punkty, których, jak sądzę, brakuje niektórych istniejących odpowiedzi.
1. Nie musisz polegać na RVO
Niektóre odpowiedzi wspominają, że musimy polegać na RVO, aby zwrócić skonstruowany
array
. To nie jest prawda; możemy skorzystać z inicjalizacji listy kopii, aby zagwarantować, że nigdy nie zostaną utworzone tymczasowe. Więc zamiast:Powinniśmy to zrobić:
2. Producent
make_array
constexpr
funkcjęTo pozwala nam tworzyć tablice stałych czasu kompilacji.
3. Nie ma potrzeby sprawdzania, czy wszystkie argumenty są tego samego typu
Po pierwsze, jeśli tak nie jest, kompilator i tak wygeneruje ostrzeżenie lub błąd, ponieważ inicjalizacja listy nie pozwala na zawężenie. Po drugie, nawet jeśli naprawdę zdecydujemy się zrobić własne
static_assert
rzeczy (być może w celu zapewnienia lepszego komunikatu o błędzie), nadal powinniśmy prawdopodobnie porównywać zepsute typy argumentów, a nie typy surowe. Na przykład,Jeśli jesteśmy po prostu
static_assert
ing żea
,b
ic
mają ten sam typ, to sprawdzenie nie powiedzie się, ale to chyba nie jest to, czego oczekujemy. Zamiast tego powinniśmy porównać ichstd::decay_t<T>
typy (czyli wszystkieint
s)).4. Zmniejsz typ wartości tablicy, rozkładając przekazane argumenty
Jest to podobne do punktu 3. Używając tego samego fragmentu kodu, ale tym razem nie określaj jawnie typu wartości:
Prawdopodobnie chcemy to zrobić
array<int, 3>
, ale implementacje w istniejących odpowiedziach prawdopodobnie nie są w stanie tego zrobić. Zamiast zwracać astd::array<T, …>
, możemy zwrócić astd::array<std::decay_t<T>, …>
.Jest jedna wada tego podejścia: nie możemy już zwracać
array
typu wartości kwalifikowanej jako cv. Ale przez większość czasu zamiast czegoś takiego jak anarray<const int, …>
, iconst array<int, …>
tak używalibyśmy a . Jest kompromis, ale myślę, że jest rozsądny. C ++ 17std::make_optional
również przyjmuje to podejście:Biorąc pod uwagę powyższe punkty, pełna działająca implementacja
make_array
w C ++ 14 wygląda następująco:Stosowanie:
źródło
C ++ 11 będzie obsługiwał ten sposób inicjalizacji dla (większości?) Standardowych kontenerów.
źródło
std::vector<>
nie potrzebuje jawnej liczby całkowitej i nie jestem pewien, dlaczegostd::array
miałby to robić .{...}
składnia implikuje stały zakres czasu kompilacji, więc ctor powinien być w stanie wydedukować zakres.std::initializer_list::size
nie jestconstexpr
funkcją i dlatego nie może być używany w ten sposób. Istnieją jednak plany z libstdc ++ (implementacja dostarczana z GCC), aby mieć swoją wersjęconstexpr
.(Rozwiązanie autorstwa @dyp)
Uwaga: wymaga C ++ 14 (
std::index_sequence
). Chociaż można by zaimplementowaćstd::index_sequence
w C ++ 11.źródło
make_array
jak w odpowiedzi Puppy.С ++ 17 kompaktowa implementacja.
źródło
Jeśli std :: array nie jest ograniczeniem i jeśli masz Boost, spójrz na
list_of()
. Nie jest to dokładnie takie samo, jak inicjalizacja tablicy typu C, której chcesz. Ale blisko.źródło
Utwórz typ kreatora tablic.
Przeciąża,
operator,
aby wygenerować szablon wyrażenia łączący każdy element z poprzednim za pomocą odwołań.Dodaj
finish
bezpłatną funkcję, która pobiera narzędzie do tworzenia tablic i generuje tablicę bezpośrednio z łańcucha odniesień.Składnia powinna wyglądać mniej więcej tak:
Nie zezwala
{}
na budowę opartą na konstrukcji, jak tylko tooperator=
robi. Jeśli chcesz skorzystać=
, możemy sprawić, że zadziała:lub
Żadne z tych rozwiązań nie wygląda na dobre.
Korzystanie z varardics ogranicza cię do narzuconego przez kompilator limitu liczby varargów i blokuje rekurencyjne użycie
{}
dla podstruktur.Ostatecznie nie ma dobrego rozwiązania.
Co mogę zrobić, to piszę mojego kodu tak zużywa zarówno
T[]
istd::array
dane agnostically - to nie obchodzi, które karmię go. Czasami oznacza to, że mój kod przekazujący musi ostrożnie przekształcić[]
tablice wstd::array
przezroczyste.źródło