2016-03-28 15 views
3

Initialisierung eines std::array von variadische Vorlage Argumenten, von einem bestimmten Index begonnen kann auf folgende Weise erfolgen:initialisieren std :: Array von Parametern Pack aus beliebigem Index

#include <array> 

template <typename T, size_t N> 
struct A 
{ 
    template <typename ... Ts> 
    A(size_t i, Ts ... vals) 
    { 
     constexpr size_t P = sizeof...(vals); 
     std::array<T, P> temp{ vals... }; 

     for (size_t j = 0; j < P; ++j) 
     { 
     arr[i + j] = temp[j]; 
     } 
    } 

    std::array<T, N> arr; 
}; 

Aber ist es möglich, das gleiche zu erreichen, ohne Konvertieren des Parameterpakets in ein temporäres Tupel oder ein anderes std :: array?

+2

@Yakk: Der ganze Sinn von C++ in diesen Tagen ist die Leistung. Temporäre Container zu erstellen steht dem im Gegensatz. Es nimmt unnötigen Platz ein und verursacht den Aufruf von unnötigen Konstruktoren, wobei möglicherweise viele Ressourcen abhängig davon zugewiesen werden, was "T" ist. Ich sehe keine Notwendigkeit, warum er rechtfertigen sollte, warum er das Parameterpack nicht in irgendeine Art von Objekt konvertieren will. –

+1

Es scheint mir Montagmorgen ist etwas schwer für Leute nach Ostern. Die Frage ist klar, aber ich sehe zwei respektvolle Mitglieder von SO, die OP mit irrelevanten Aussagen anstürmen, anstatt die Frage zu beantworten. – SergeyA

+1

@NicolBolas Das sind alles gute Gründe. Aber eine 'std :: tie' ist ein Container von Referenzen, deren Existenz und Größe (theoretisch) entzogen werden kann. Und es ist ein Tupel. – Yakk

Antwort

3

können Sie verwenden std::index_sequence und Delegieren Konstruktor:

template <typename T, size_t N> 
struct A 
{ 
    template <typename ... Ts> 
    A(size_t i, Ts&& ... vals) 
    : 
     A(std::index_sequence_for<Ts...>{}, i, std::forward<Ts>(vals)...) 
    {} 

    template <std::size_t...Is, typename ... Ts> 
    A(std::index_sequence<Is...>, size_t i, Ts&& ... vals) 
    { 
     int dummy[] = {0, ((arr[i + Is] = vals), void(), 0)...}; 
     static_cast<void>(dummy); // Avoid warning for unused variable 
    } 

    std::array<T, N> arr; 
}; 

Oder mit Folding Expression von C 17 ++,

template <std::size_t...Is, typename ... Ts> 
    A(std::index_sequence<Is...>, size_t i, Ts&& ... vals) 
    { 
     (static_cast<void>(arr[i + Is] = vals), ...); 
    } 
+0

@SergeyA In den Kommentaren OP angegeben will er Laufzeitkosten vermeiden. –

+2

@Yakk: mit Ausdrucksfaltung von C++ 1z kann das Dummy-Array entfernt werden: '((arr [i + Is] = vals), ...)'. – Jarod42

+0

@Yakk Eigentlich denke ich in diesem Fall die Konstruktion des Arrays == nop. –

2
template<std::size_t I> 
using index_t = std::integral_constant<std::size_t, I>; 

template<class T, std::size_t=0, class...Args> 
T make(Args&&...args){ return T(std::forward<Args>(args)...); } 

template <class T, size_t N> 
struct A { 
    template <std::size_t I, class...Ts> 
    A(index_t<I>, Ts&&... vals): 
    A(std::make_index_sequence<I>{}, std::forward<Ts>(vals)...) 
    {} 
    template <std::size_t...Is, class...Ts> 
    A(std::index_sequence<Is...>, Ts&&...vals): 
    arr{{ make<T,Is>()..., std::forward<Ts>(vals)... }} 
    {} 
    // ... 
}; 

dies erfordert eine Kompilierung-Offset bekannt, aber vermeidet Erstellen eines standardkonstruierten Arrays, das dann zugewiesen wird. Es beinhaltet jedoch auch das Erstellen eines Stapels von T s und das Verschieben dieser in das Array. Ick!

Backup eine Sekunde, wir wissen, dass das Array ein Standard-Layout-Block von T s ist. Lass uns betrügen.

// stores the raw buffer for the array, and constructs it with 
// some care: 
template<class T> 
using storage = std::aligned_storage_t<sizeof(T),alignof(T)>; 
template<class T, size_t N> 
struct A_storage { 
    storage<std::array<T, N>> raw_arr; 
    std::array<T,N>& arr() { 
    return *reinterpret_cast<std::array<T, N>*>(&raw_arr); 
    } 
    std::array<T,N> const& arr() const { 
    return *reinterpret_cast<std::array<T, N> const*>(&raw_arr); 
    } 

    template<class...Ts> 
    static void make_arr(storage<std::array<T, N>>& retval, std::size_t offset, Ts&&...ts) { 
    auto* ptr = reinterpret_cast<T*>(&retval); 
    try { 
     std::size_t ctor_count = 0; 
     for (size_t i = 0; i < offset; ++i) { 
     ++ctor_count; 
     ::new((void*)(ptr+i)) T(); 
     } 
     ::new((void*)(ptr +offset)) std::array<T, sizeof...(Ts)>{{ 
     (++ctor_count, void(), std::forward<Ts>(ts))... 
     }}; 
     for (size_t i = offset+sizeof...(Ts); i < N; ++i) { 
     ++ctor_count; 
     ::new((void*)(ptr+i)) T(); 
     } 
    } catch(...) { 
     // ctor_count is the count of constructors *attempted* 
     // so ptr[ctor_count-2] is the last one we *succeeded at* 
     // destroy everything we successfully constructed. 
     // ctor_count has to be at least 1, as we cannot throw before 
     // incrementing. Let us peel it off. 
     --ctor_count; 
     // iterate backwards from ctor_count-1 down to 0, so we destroy 
     // in reverse order of constructing: 
     for (size_t i = 1; i <= ctor_count; ++i) { 
     ptr[ctor_count-i].~T(); 
     } 
     throw; 
     // I use attempted, because the initializer list syntax of array 
     // construction doesn't let me increment after I provide the value 
     // for the place I'm constructing. I can, however, increment before. 

    } 
    } 
    template<class...Ts> 
    A_storage(std::size_t offset, Ts&&...ts) 
    { 
    static_assert(sizeof...(Ts)<=N, "too much data!"); 
    ASSERT(offset+sizeof...(Ts)<=N); 
    make_arr(raw_arr, offset, std::forward<Ts>(ts)...); 
    } 
    // only runs if the ctor above completes, which means 
    // everything was constructed: 
    ~A_storage() { 
    for (size_t i = 1; i <= N; ++i) { 
     arr()[N-i].~T(); 
    } 
    } 
}; 
template<std::size_t N, class T> 
struct A:A_storage { 
    template<class...Ts> 
    A(std::size_t offset, Ts&&...ts): 
    A_storage(offset, std::forward<Ts>(ts)...) 
    {} 
}; 

Alles ist an Ort und Stelle konstruiert. Keine Notwendigkeit für T(T&&) Unterstützung! (Es sei denn, Sie geben die Parameter an das Array weiter, aber das ist nicht Ihr Problem)

Ich habe versucht, die Vernichtung in einer ausnahmsweisen Umgebung durchzuführen. Ich habe es vielleicht falsch verstanden.

Die einzige temporäre Variable (der Rückgabewert make_arr) sollte von NRVO entfernt werden. Auf der anderen Seite könnte ein Compiler ein Faulenzer sein und nicht in Konstrukte von Klassenmitgliedern. Schlechter Compiler

+0

Hmm, sind die initialisierten 'T's in der ersten Version nicht für elision geeignet? –

+0

... [yep] (http://coliru.stacked-crooked.com/a/5f164c79e853b197). Das einzige Problem besteht also darin, dass "T" beweglich konstruiert werden muss, aber das wird durch eine garantierte Kopie gelöst. –

+1

@ T.C. Ich denke, der ',' Teil ruiniert das? Vielleicht nicht? Ich denke, ich könnte schreiben: template T make (Argumente && ... args) {return T (std :: vorwärts (args) ...); } ', dann ersetze' (Ist, void(), T()) ... 'mit' make () ... '. Das beseitigt ',' Operatormissbrauch, ein Plus. Ersetzt. v1 benötigt außerdem einen bekannten bekannten Offset, den das OP nicht im Code hat, so dass v2 immer noch benötigt wird. – Yakk

0

Obwohl es C++ 17-fache Ausdrücke beinhaltet, die eine möglichst einfache Lösung durch den Kommentar von Jarod42 inspiriert ist:

#include <array> 

template <typename T, size_t N> 
struct A 
{ 
    template <typename ... Ts> 
    A(Ts ... vals) 
    { 
    size_t i = 1; // starting index, can be an argument 
    ((arr[i++] = vals) , ...); 
    } 

    std::array<T, N> arr; 
}; 

int main() 
{ 
    A<int, 4> a(1, 2, 3); 

    return 0; 
} 

diese Kompilieren mit Clang 3.6 oder oben mit Optimierungsstufe -O1 und -std=c++1z Flagge der erzeugt Montage ist:

A<int, 4ul>::A<int, int, int>(int, int, int):    # @A<int, 4ul>::A<int, int, int>(int, int, int) 
pushq %rbp 
pushq %r15 
pushq %r14 
pushq %rbx 
pushq %rax 
movl %ecx, %r14d 
movl %edx, %r15d 
movl %esi, %ebx 
movq %rdi, %rbp 
movl $1, %esi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %ebx, (%rax) 
movl $2, %esi 
movq %rbp, %rdi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %r15d, (%rax) 
movl $3, %esi 
movq %rbp, %rdi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %r14d, (%rax) 
addq $8, %rsp 
popq %rbx 
popq %r14 
popq %r15 
popq %rbp 
retq 

, die zu den Testfall entspricht

template <typename T, size_t N> 
struct B 
{ 
    B(T x, T y, T z) 
    { 
    arr[1] = x; 
    arr[2] = y; 
    arr[3] = z; 
    } 

    std::array<T, N> arr; 
}; 

int main() 
{ 
    B<int, 4> b(1, 2, 3); 

    return 0; 
} 

mit generierte Assembly

B<int, 4ul>::B(int, int, int):      # @B<int, 4ul>::B(int, int, int) 
pushq %rbp 
pushq %r15 
pushq %r14 
pushq %rbx 
pushq %rax 
movl %ecx, %r14d 
movl %edx, %r15d 
movl %esi, %ebx 
movq %rdi, %rbp 
movl $1, %esi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %ebx, (%rax) 
movl $2, %esi 
movq %rbp, %rdi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %r15d, (%rax) 
movl $3, %esi 
movq %rbp, %rdi 
callq std::array<int, 4ul>::operator[](unsigned long) 
movl %r14d, (%rax) 
addq $8, %rsp 
popq %rbx 
popq %r14 
popq %r15 
popq %rbp 
retq 

Es lohnt sich zu beachten, dass die erzeugte Anordnung der beiden Fall entspricht.

Verwandte Themen