2016-03-27 12 views
8

To interesujący problem, który myślę o czasach wcześniej. Biorąc pod uwagę struct z podstawowej kruszywa:Uniwersalna inicjacja agregacji za pomocą szablonów variadic

#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 
}; 

Jak wdrożyć zmiennej liczbie argumentów szablonu konstruktora A(const T& value, const Ts& ... values) do

  • zaakceptować obie wartości typu T i inny A<T, N>
  • właściwie zainicjowana bazowego kruszywo na podstawie wartości reprezentowanych przez przekazane argumenty:
  • respektować zdolności kruszywa
  • wsparcia C++ 14 zasad constexpr i nie wprowadzają żadnego narzutu wykonania

spełniających powyższe wymagania, można wykonać następujące czynności:

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

Jaka jest oczekiwana wartość 'y [2]' w 'A y (1,2);'? –

+0

A co z obciążeniem środowiska wykonawczego wywołania konstruktora? –

+0

@VaughnCato dzięki za złapanie, który był błędem, zaktualizowany teraz – plasmacel

Odpowiedz

9

Oto jedno podejście, które działa, ale prawie na pewno można je poprawić.

Mamy konstruktora dla A, który pobiera pakiet parametrów, przekształca każdy element w krotkę, łączy krotki razem, tworząc jedną dużą krotkę, a następnie po prostu używa agregacyjnej inicjalizacji z tej dużej krotki. Wszystkie poniższe mogą być constexpr, po prostu pominięte dla zwięzłości.

Najpierw robimy konwersję:

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

With:

// 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)...); 
} 

A potem po prostu zainicjować stamtąd:

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

Jedyne co mogę dodać to użyć 'forward_as_tuple' zamiast tego, aby zmniejszyć kopiowanie . –

+0

@ T.C. Dobra uwaga, dzięki! Naprawiony. – Barry

5

Odpowiedź @Barry jest z pewnością poprawna i do zaakceptowania. Wymaga to jednak pewnych dodatków do biblioteki C++ 14 (które prawdopodobnie można również napisać samemu w C++ 11), a ogólnie wymaga dobrego tuple - i meta-programowania fu.

Zobaczmy wiele argumentów jako "zakres zakresów", gdzie zakres to tylko wskaźnik i rozmiar. Argumenty skalarne to tylko zakres wielkości 1, a argumenty A<T, N> to zakresy wielkości 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 };  
} 

Następnie można po prostu zrobić podwójną pętlę nad wszystkimi elementami wszystkich zakresach

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 pracy w czasie kompilacji (wymaga GCC> = 6,0 lub dzyń> = 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

Chociaż samo podejście jest poprawne i nieco łatwiejsze do naśladowania (ze względu na brak szablonu meta-programowania), _ "nie wprowadzaj żadnych obciążeń środowiska wykonawczego" _ pominięto specyfikację. – edmz

+0

@ Czarne dzięki, FTFY. Zauważ, że wymaga to zmiany elementu danych z 'std :: array ' na 'T [N]' C-array, ponieważ niestanowiący 'operator []' dla poprzedniego nie jest 'constexpr'. – TemplateRex

Powiązane problemy