c++11 constexpr flatten list of std::array into array c++11 constexpr flatten list of std::array into array arrays arrays

c++11 constexpr flatten list of std::array into array


Notice - I understood your question as follows: you want to join those two arrays and flatten the result into a single, new array containing the concatenation of their elements.

You can accomplish your goal with three C++11+ concepts:

  1. Variadic templates
  2. constexpr expressions
  3. Parameter pack

You start by creating a template (an empty shell) to start designing your recursive-fashion list flattening function:

template<unsigned N1, unsigned N2>constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){  // TODO}

so far so good: the constexpr specifier will hint the compiler to compile-time evaluate that function each time it can.

Now for the interesting part: std::array has (since c++1y) a constexpr overload for the operator[], this means you can write something like

template<unsigned N1, unsigned N2>constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){  return std::array<int,N1+N2>{a1[0],a1[1],a1[2],a2[0],a2[1]};}

(notice the aggregate-initialization to initialize the object from a series of integer values)

Obviously manually hard-coding all the index accesses to the values of the two arrays is no better than just declaring the concatenated array itself. The concept that will save the day is the following: Parameter Packs. A template parameter pack is a template parameter that accepts 0 or more template arguments. A template with at least one parameter pack is called variadic template.

The cool thing is the ability of expanding the parameter pack into specified locations like:

#include <iostream>#include <array>template<unsigned... Num>std::array<int, 5> function(const std::array<int,5>& source) {    return std::array<int,5>{source[Num]...};}int main() {    std::array<int,5> source{7,8,9,10,11};    std::array<int,5> res = function<0,1,2,3,4>(source);    for(int i=0; i<res.size(); ++i)        std::cout << res[i] << " "; // 7 8 9 10 11    return 0;}

So the only thing we need right now is to be able to compile-time generate the "index series" like

std::array<int,5> res = function<0,1,2,3,4>(source);                                 ^ ^ ^ ^ ^

At this point we can again take advantage of the parameter packs in conjunction with an inheritance mechanism: the idea is to have a deeply nested hierarchy of derived : base : other_base : another_base : ... classes which would "accumulate" the indices into the parameter pack and terminate the "recursion" when the index reaches 0. If you didn't understand the previous sentence don't worry and take a look at the following example:

std::array<int, 3> a1{42,26,77};// goal: having "Is" = {0,1,2} i.e. a1's valid indicestemplate<unsigned... Is> struct seq;

we can generate a sequence of indices in the following way:

template<unsigned N, unsigned... Is>struct gen_seq : gen_seq<N-1, Is...>{}; // each time decrement the index and go ontemplate<unsigned... Is>struct gen_seq<0 /*stops the recursion*/, Is...> : /* generate the sequence */seq<Is...>{};std::array<int, 3> a1{42,26,77};gen_seq<3>{};

There's something missing anyway: the code above will start with gen_seq<3, (nothing)> and instantiate the specified template which will instantiate the gen_seq<2, (nothing)> as its base class that will instantiate the gen_seq<1, (nothing)> as its base class that will instantiate the gen_seq<0, (nothing)> as its base class that will instantiate the seq<(nothing)> as final sequence.

The sequence is '(nothing)', something is wrong..

In order to "accumulate" the indices into the parameter pack you need to "add a copy" of the decreased index to the parameter pack at each recursion:

template<unsigned N, unsigned... Is>struct gen_seq : gen_seq<N-1, /*This copy goes into the parameter pack*/ N-1, Is...>{};template<unsigned... Is>struct gen_seq<0 /*Stops the recursion*/, Is...> : /*Generate the sequence*/seq<Is...>{};template<unsigned... Is> struct seq{};// Using '/' to denote (nothing)gen_seq<3,/> : gen_seq<2, 2,/> : gen_seq<1,  1,2,/> : gen_seq<0, 0,1,2,/> : seq<0,1,2,/> .

so now we're able to recollect all the pieces together and generate two sequences of indices: one for the first array and one for the second array and concatenate them together into a new return array which will hold the concatenated and flattened union of the two arrays (like appending them together).

The following code, at this point, should be easily comprehensible:

#include <iostream>#include <array>template<unsigned... Is> struct seq{};template<unsigned N, unsigned... Is>struct gen_seq : gen_seq<N-1, N-1, Is...>{};template<unsigned... Is>struct gen_seq<0, Is...> : seq<Is...>{};template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>// Expansion packconstexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2, seq<I1...>, seq<I2...>){  return { a1[I1]..., a2[I2]... };}template<unsigned N1, unsigned N2>// Initializer for the recursionconstexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){  return concat(a1, a2, gen_seq<N1>{}, gen_seq<N2>{});}int main() {    constexpr std::array<int, 3> a1 = {1,2,3};    constexpr std::array<int, 2> a2 = {4,5};    constexpr std::array<int,5> res = concat(a1,a2);    for(int i=0; i<res.size(); ++i)        std::cout << res[i] << " "; // 1 2 3 4 5    return 0;}

http://ideone.com/HeLLDm


References:

https://stackoverflow.com/a/13294458/1938163

http://en.cppreference.com/

http://en.wikipedia.org


With C++1y, an implementation may (although it is not required) allow std::tuple_cat to work with any tuple-like types, and not just std::tuple<T...>. In our case, std::array<T, N> is such a type. So we could attempt:

constexpr std::array<int, 3> a1 = {1, 2, 3};constexpr std::array<int, 2> a2 = {4, 5};constexpr auto a3 = std::tuple_cat(a1, a2);// note:// not possible// constexpr auto e = a3[3]// insteadconstexpr auto e = std::get<3>(a3);

As it so happens though, the result of a call to std::tuple_cat is a tuple, not an array. It is then possible to turn an std::tuple<T, T,… , T> into an std::array<T, N>:

template<    typename Tuple,    typename VTuple = std::remove_reference_t<Tuple>,    std::size_t... Indices>constexpr std::array<    std::common_type_t<std::tuple_element_t<Indices, VTuple>...>,    sizeof...(Indices)>to_array(Tuple&& tuple, std::index_sequence<Indices...>){    return { std::get<Indices>(std::forward<Tuple>(tuple))... };}template<typename Tuple, typename VTuple = std::remove_reference_t<Tuple>>constexpr decltype(auto) to_array(Tuple&& tuple){    return to_array(        std::forward<Tuple>(tuple),        std::make_index_sequence<std::tuple_size<VTuple>::value> {} );}

(As it turns out this to_array implementation converts any tuple-like into an array as long as the tuple element types are compatible.)

Here’s a live example for GCC 4.8, filling in some of the C++1y features not yet supported.


Luc's post answer the question.
But for fun, here is a C++14 solution without template metaprogramming, just pure constexpr.

There is a catch though, generalized constexpr was voted into the standard core language more than one year ago, but the STL still isn't updated yet...

As an experiment, open the header <array> and add an obviously missing constexpr for non-const operator[]

constexpr reference operator[](size_type n);

Also open <numeric> and turn std::accumulate into a constexpr function

template <class InputIterator, class T>constexpr T accumulate(InputIterator first, InputIterator last, T init);

Now we can do :

#include <iostream>#include <array>#include <numeric>template <typename T, size_t... sz>constexpr auto make_flattened_array(std::array<T, sz>... ar){   constexpr size_t NB_ARRAY = sizeof...(ar);   T* datas[NB_ARRAY] = {&ar[0]...};   constexpr size_t lengths[NB_ARRAY] = {ar.size()...};   constexpr size_t FLATLENGTH = std::accumulate(lengths, lengths + NB_ARRAY, 0);   std::array<T, FLATLENGTH> flat_a = {0};   int index = 0;   for(int i = 0; i < NB_ARRAY; i++)   {      for(int j = 0; j < lengths[i]; j++)      {         flat_a[index] = datas[i][j];         index++;      }   }   return flat_a;}int main(){  constexpr std::array<int, 3> a1 = {1,2,3};  constexpr std::array<int, 2> a2 = {4,5};  constexpr std::array<int, 4> a3 = {6,7,8,9};  constexpr auto a = make_flattened_array(a1, a2, a3);  for(int i = 0; i < a.size(); i++)     std::cout << a[i] << std::endl;}

(Compile and run on clang trunk)