Growing std::array

Adding some more convenience functions to std::array

Today I will add some more functions for std::array
  • append

  • prepend

  • appendx

  • join

These functions might, under the same or a similar name, already exist, if you know where, please let me know.

These functions are useful when
  • std::array is the right type,

  • you want to get a new array

    • with one more element at the end

    • with one more element at the begin

    • with some more elements at the end

    • as a result of joining 2 arrays

Today’s subtopic, my first time using index sequences

Implementing these function let me also use std::make_index_sequence for the first time, so this was a great lesson for me.
As usual, without help it would have been much much harder for me to figured out how this works, so thanks on this place to Simon Brand (helped most) , Björn Fahller and of course all the others who where actively involved in the discussion about that topic. Sorry if I have forgotten to mention someone.

The implementation

It is obvious, that since you get a new element, one copy and some moves are required to create the new array, thought, the one copy can be changed into a move when the user of the functions decides to move the input array into the function.

Appending one element to an array

template<typename T, size_t S, std::size_t... Idx>
constexpr auto
append (std::array<T, S>&& data, T&& elem, std::index_sequence<Idx...>)
  ->  std::array<T, S+1>
{
  return {std::get<Idx>(std::forward<std::array<T, S>>(data))...,
          std::forward<T>(elem)} ;
}

template<typename T, size_t S>
constexpr auto
append (std::array<T, S> data, T elem)
{
    return append(std::move(data),
                  std::move(elem),
                  std::make_index_sequence<S>{});
}

using this function will work like this

  std::array<char, 2> a2 = {'b', 'c'} ;
  for (auto& e : append(a2, 'd'))
    std::cout << e ;

and will, less surprising, print bcd

Prepending one element to an array

For reasons of symmetry, and since this was my original use case, the prepend version. It works, like append and just flips the order of the arguments,

template<typename T, size_t S, std::size_t... Idx>
constexpr auto
prepend (std::array<T, S>&& data, T&& elem, std::index_sequence<Idx...>)
  ->  std::array<T, S+1>
{
  return {std::forward<T>(elem),
          std::get<Idx>(std::forward<std::array<T, S>>(data))...} ;
}

template<typename T, size_t S>
constexpr auto
prepend (T elem, std::array<T, S> data)
  -> std::array<T, S+1>
{
    return prepend(std::move(data),
                  std::move(elem),
                  std::make_index_sequence<S>{});
}

Usage is the same as of append

  std::array<char, 2> a2 = {'b', 'c'} ;
  for (auto& e : prepend('a', a2))
    std::cout << e ;

and will, less surprising, print abc

Appending multiple elements

Since we have variadic templates it is of course possible to add multiple elements to an array with one function call

I will call this function appendx, and it looks like this

template<typename T, size_t S, typename... Ts, std::size_t... Idx>
constexpr auto
appendx(std::index_sequence<Idx...>, std::array<T, S>&& data, Ts&&... elems)
->  std::array<T, S + sizeof...(Ts)>
{
   return {std::get<Idx>(std::forward<std::array<T, S>>(data))...,
           std::forward<Ts>(elems)...} ;
}

template<typename T, size_t S, typename... Ts>
constexpr auto
appendx(std::array<T, S> data, Ts... elems)
{
   return appendx(std::make_index_sequence<S>{},
                  std::move(data),
                  std::move(elems)...);
}

Using appendx works like this

std::array<char, 2> a2 = {'b', 'c'} ;
for (auto& e : appendx(a2, 'd', 'e', 'f'))
  std::cout << e ;
std::cout << std::endl ;

what will of course print bcdef

Why appendx ?

There is reason why I have an append and appendx function.
appendx would also work with just one element and do the same as append.

But I do not know how to write a prepend function that takes multiple elements before the array as arguments. and i do not like
prepend(array, 'a','b','c')
since it should be
prepend('a','b','c', array)

Challenge for the reader

Therefore is of course a pity that I could not figure out how to write the prepenx function that works like that:
prepend('a','b','c', array)

If you know how this could work, please let me know!

It is, however possible to prepend multiple elements in front of an existing array, simply by using a join method.

Joining 2 arrays to one

The implementation for joining 2 arrays into a new one looks like that

template<typename T, size_t S1, std::size_t... Idx1,
                     size_t S2, std::size_t... Idx2>
auto join(std::index_sequence<Idx1...>, std::array<T, S1>&& a1,
                std::index_sequence<Idx2...>, std::array<T, S2>&& a2)
-> std::array<T, S1 + S2>
{
   return {std::get<Idx1>(std::forward<std::array<T, S1>>(a1))...,
           std::get<Idx2>(std::forward<std::array<T, S2>>(a2))...} ;
}


template<typename T, size_t S1, size_t S2>
auto join (std::array<T, S1> a1, std::array<T, S2> a2)
-> std::array<T, S1 + S2>
{
  return join (std::make_index_sequence<S1>{},
               std::move(a1),
               std::make_index_sequence<S2>{},
               std::move(a2)
              ) ;
}

Using the join method works, for example, like this

  std::array<char, 2> a2 = {'b', 'c'} ;
  auto aa = join(make_array('z', 'a'), std::move(a2));
  for (auto& e : aa)
    std::cout <<  e;

This will print zabc.

The copy and past version of the code

You can play with the code here

http://rextester.com/SGXM67874

#include <array>
#include <iostream>
#include <utility>

template<typename... Args>
constexpr
std::array<typename std::common_type<Args...>::type, sizeof...(Args)>
make_array(Args&&... args)
{
  return std::array<typename std::common_type<Args...>::type,
      sizeof...(Args)>{  std::forward<Args>(args)... };
}



template<typename T, size_t S, std::size_t... Idx>
constexpr auto
append (std::array<T, S>&& data, T&& elem, std::index_sequence<Idx...>)
  ->  std::array<T, S+1>
{
  return {std::get<Idx>(std::forward<std::array<T, S>>(data))...,
          std::forward<T>(elem)} ;
}


template<typename T, size_t S>
constexpr auto
append (std::array<T, S> data, T elem)
{
    return append(std::move(data),
                  std::move(elem),
                  std::make_index_sequence<S>{});
}


template<typename T, size_t S, std::size_t... Idx>
constexpr auto
prepend (std::array<T, S>&& data, T&& elem, std::index_sequence<Idx...>)
  ->  std::array<T, S+1>
{
  return {std::forward<T>(elem),
          std::get<Idx>(std::forward<std::array<T, S>>(data))...} ;
}

template<typename T, size_t S>
constexpr auto
prepend (T elem, std::array<T, S> data)
  -> std::array<T, S+1>
{
    return prepend(std::move(data),
                  std::move(elem),
                  std::make_index_sequence<S>{});
}



template<typename T, size_t S, typename... Ts, std::size_t... Idx>
constexpr auto
appendx(std::index_sequence<Idx...>, std::array<T, S>&& data, Ts&&... elems)
->  std::array<T, S + sizeof...(Ts)>
{
   return {std::get<Idx>(std::forward<std::array<T, S>>(data))...,
           std::forward<Ts>(elems)...} ;
}

template<typename T, size_t S, typename... Ts>
constexpr auto
appendx(std::array<T, S> data, Ts... elems)
{
   return appendx(std::make_index_sequence<S>{},
                 std::move(data), std::move(elems)...);
}


template<typename T, size_t S1, std::size_t... Idx1,
                     size_t S2, std::size_t... Idx2>
auto join(std::index_sequence<Idx1...>, std::array<T, S1>&& a1,
                std::index_sequence<Idx2...>, std::array<T, S2>&& a2)
-> std::array<T, S1 + S2>
{
   return {std::get<Idx1>(std::forward<std::array<T, S1>>(a1))...,
           std::get<Idx2>(std::forward<std::array<T, S2>>(a2))...} ;
}


template<typename T, size_t S1, size_t S2>
auto join (std::array<T, S1> a1, std::array<T, S2> a2)
-> std::array<T, S1 + S2>
{
  return join (std::make_index_sequence<S1>{},
               std::move(a1),
               std::make_index_sequence<S2>{},
               std::move(a2)
              ) ;
}


int main()
{
  std::array<char, 2> a2 = {'b', 'c'} ;

  for (auto& e : append(a2, 'd'))
    std::cout << e ;
  std::cout << std::endl ;

  for (auto& e : prepend('a', a2))
    std::cout << e ;
  std::cout << std::endl ;

  for (auto& e : appendx(a2, 'd', 'e', 'f'))
    std::cout << e ;
  std::cout << std::endl ;

  auto aa = join(make_array('z', 'a'), std::move(a2));
  for (auto& e : aa)
    std::cout <<  e;

  std::cout << std::endl ;
}

Some notes

Of course, having the join method you could ask, why append, appendx and prepend.
And the answer is: It was fun to write and when using these functions it’s more easy to read and see what is going on than always use join.

Since there is something like tuple size, append and pre-pend should also be possible with std::tuple, but this might already exist. The join version is already available via tuple_cat and make_tuple is also available. If I find no existing implementation this might be a topic for a future blog.

Update 1

adopt code samples after feedback (thanks Simon Brand)
use std::move instead of std::forward…​. where it fits better.

As usual

If you find any mistakes, in the code or in my spelling, please add a comment below, ideas for improvements are also very much welcome.