2016-03-27 40 views
8

Questo è un problema interessante a cui sto pensando un po 'di tempo fa. Dato un struct con un aggregato sottostante:Inizializzazione di aggregati universali con modelli variadici

#include <array> 

template <typename T, size_t N> 
struct A 
{ 
    constexpr A() = default; 

    template <typename ... Ts> 
    constexpr A(const T& value, const Ts& ... values); // magic 

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

Come si implementa variadic modello costruttore A(const T& value, const Ts& ... values) al

  • accettare entrambi i valori di tipo T e un altro A<T, N>
  • inizializzare correttamente l'aggregato sottostante in base ai valori rappresentati dagli argomenti passati
  • rispettare la capacità degli aggregati
  • supporto C++ 14 regole constexpr e non introdurre alcun overhead runtime

soddisfano i requisiti di cui sopra, è possibile eseguire le seguenti operazioni:

int main() 
{ 
    A<int, 3> x(1, 2, 3); 
    A<int, 2> y(1, 2); 

    A<int, 6> a(x, 1, 2, 3); 
    A<int, 6> b(1, x, 2, 3); 
    A<int, 6> c(1, 2, x, 3); 
    A<int, 6> d(1, 2, 3, x); 
    A<int, 6> e(x, x); 
    A<int, 6> f(y, y, y); 

    return 0; 
} 
+1

Qual è il valore atteso di 'y [2]' in 'A y (1,2);'? –

+0

E il sovraccarico di runtime del richiamo del costruttore? –

+0

@VaughnCato grazie per la cattura, quello era un errore, aggiornato ora – plasmacel

risposta

9

Ecco un approccio che funziona, ma potrebbe quasi certamente essere migliorato.

Abbiamo un costruttore per A che accetta un pacchetto di parametri, converte ciascun elemento in una tupla, concatena le tuple per creare un'unica tupla e quindi semplicemente utilizza l'inizializzazione di aggregazione da quella grande tupla. Tutto quanto segue può essere constexpr, l'ho appena omesso per brevità.

Per prima cosa fare la conversione:

template <class... Us> 
A(Us const&... us) 
: A(std::tuple_cat(as_tuple(us)...)) 
{ } 

Con:

// single argument 
template <class U> 
auto as_tuple(U const& u) { 
    return std::forward_as_tuple(u); 
} 

// aggregate argument 
template <size_t M> 
auto as_tuple(A<T, M> const& a) { 
    return as_tuple(a, std::make_index_sequence<M>{}); 
} 

template <size_t M, size_t... Is> 
auto as_tuple(A<T, M> const& a, std::index_sequence<Is...>) { 
    return std::forward_as_tuple(std::get<Is>(a.arr)...); 
} 

E poi abbiamo appena inizializzare da lì:

template <class... Us, class = std::enable_if_t<(sizeof...(Us) <= N)>> 
A(std::tuple<Us...> const& t) 
: A(t, std::index_sequence_for<Us...>{}) 
{ } 

template <class... Us, size_t... Is> 
A(std::tuple<Us...> const& t, std::index_sequence<Is...>) 
: arr{{std::get<Is>(t)...}} 
{ } 

Demo

+2

L'unica cosa che aggiungerei è usare 'forward_as_tuple' invece, per ridurre la copia . –

+0

@ T.C. Buon punto, grazie! Fisso. – Barry

5

La risposta di @Barry è certamente corretta e accettabile. Ma richiede alcune aggiunte alle librerie C++ 14 (che probabilmente potresti scrivere anche tu in C++ 11), e nel complesso richiede alcuni buoni tuple - e la meta-programmazione fu.

Vediamo più argomenti un "intervallo di intervalli", in cui un intervallo è solo un puntatore e una dimensione. Gli argomenti scalari sono solo un intervallo di dimensione 1 e gli argomenti A<T, N> sono intervalli di dimensioni N.

template<class T> 
struct Range 
{ 
    T const* data_; 
    std::size_t size_; 

    constexpr T const* begin() const noexcept { return data_; } 
    constexpr T const* end() const noexcept { return data_ + size_; } 
    constexpr std::size_t size() const noexcept { return size_; } 
}; 

template<class T> 
constexpr Range<T> as_range(T const& t) 
{ 
    return { &t, 1 }; 
} 

template<class T, std::size_t N> 
struct A; 

template<class T, std::size_t N> 
constexpr Range<T> as_range(A<T, N> const& a) 
{ 
    return { a.arr, N };  
} 

Si può poi semplicemente fare un doppio ciclo su tutti gli elementi di tutte le fasce

template <typename T, size_t N> 
struct A 
{ 
    T arr[N]; // aggregate 

    constexpr A() = default; 

    template <typename U, typename... Us> 
    constexpr A(U const u, Us const&... us) 
    : 
     arr{} 
    { 
     Range<T> rngs[1 + sizeof...(Us)] { as_range(u), as_range(us)... }; 
     auto i = 0; 
     for (auto const& r : rngs) 
      for (auto const& elem : r) 
       arr[i++] = elem; 
     assert(i == N);     
    } 
}; 

Live Example lavora a tempo di compilazione (richiede GCC> = 6.0 o Clang> = 3.4)

template <class T, size_t N> 
void print(A<T, N> const& a) { 
    for (T const& t : a.arr) { 
     std::cout << t << ' '; 
    } 
    std::cout << '\n'; 
} 

int main() 
{ 
    constexpr A<int, 3> x(1, 2, 3); 
    constexpr A<int, 2> y(1, 2); 

    constexpr A<int, 6> a(x, 1, 2, 3); 
    constexpr A<int, 6> b(1, x, 2, 3); 
    constexpr A<int, 6> c(1, 2, x, 3); 
    constexpr A<int, 6> d(1, 2, 3, x); 
    constexpr A<int, 6> e(x, x); 
    constexpr A<int, 6> f(y, y, y); 

    print(a); // 1 2 3 1 2 3 
    print(b); // 1 1 2 3 2 3 
    print(c); // 1 2 1 2 3 3 
    print(d); // 1 2 3 1 2 3 
    print(e); // 1 2 3 1 2 3 
    print(f); // 1 2 1 2 1 2  
} 
+0

Sebbene l'approccio stesso sia valido e in qualche modo più semplice da seguire (a causa dell'assenza di meta-programmazione template), il _ "non introduce alcun sovraccarico di runtime" _ la specifica è stata persa. – edmz

+0

@black grazie, FTFY. Si noti che ciò richiede la modifica del membro dei dati da 'std :: array ' a un array C 'T [N]' poiché l'operatore non-const '[]' per il primo non è 'constexpr'. – TemplateRex

Problemi correlati