2

How can I construct an std::array out of several smaller ones, plus not-array elements?

I.e.:

std::array<std::string, 2> strings = { "a", "b" };
std::array<std::string, 2> more_strings = { "c", "d" };
std::string another_string = "e";

std::array<std::string, 5> result = {strings, more_strings, another_string};

The same question applies to initializing std::initializer_list with smaller std::initializer_lists.

Alexandr Zarubkin
  • 899
  • 1
  • 11
  • 26
  • 1
    It's not initialization, but you can write a function with a parameter pack that fills the array. – Karoly Horvath Nov 27 '15 at 10:29
  • 1
    The syntax you tried isn't possible. Of course you could write `{ strings[0], strings[1], more_strings[0], more_strings[1], another_string }` – M.M Nov 27 '15 at 10:29
  • @KarolyHorvath in that case the strings are default-constructed and then assigned, which is a small waste that I suppose OP wants to avoid – M.M Nov 27 '15 at 10:29
  • I think you'll have to move to another container if you want to avoid the problem of default-construction ; `array` isn't particularly flexible. E.g. you could use `vector` and use emplacement to add the ranges. But this is probably worse overall than the array. – M.M Nov 27 '15 at 10:31
  • @M.M Yes, I know that my syntax doesn't compile, but I'm looking for an alternative which is more or less convenient. – Alexandr Zarubkin Nov 27 '15 at 10:32
  • 1
    There is no `std::initializer_list` in your code. Brace initialisation != `std::initializer_list`. – Simple Nov 27 '15 at 10:41
  • @Simple Yes, my question consists of two parts: the first with std::array and the second with std::array changed to std::initializer_list everywhere. – Alexandr Zarubkin Nov 27 '15 at 10:49

2 Answers2

2

If it's possible for you, you might find tuples easier (and more efficient) to work with until the final creation of the array.

conceptually, you could write something like this:

    using namespace std;
    auto strings = make_tuple( "a"s, "b"s );
    auto more_strings = make_tuple( "c"s, "d"s);
    auto another_string = make_tuple("e"s);
    auto result = to_array(tuple_cat(strings, more_strings, another_string));

Of course you'll need an implementation for to_array which is not trivial. The full code is below, with credit going to Luc Danton in this answer: Convert std::tuple to std::array C++11

full code for reference:

#include <iostream>
#include <string>
#include <tuple>
#include <array>


template<int... Indices>
struct indices {
    using next = indices<Indices..., sizeof...(Indices)>;
};

template<int Size>
struct build_indices {
    using type = typename build_indices<Size - 1>::type::next;
};

template<>
struct build_indices<0> {
    using type = indices<>;
};

template<typename T>
using Bare = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template<typename Tuple>
constexpr
typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
make_indices()
{ return {}; }

template<typename Tuple, int... Indices>
std::array<
typename std::tuple_element<0, Bare<Tuple>>::type,
std::tuple_size<Bare<Tuple>>::value
>
to_array(Tuple&& tuple, indices<Indices...>)
{
    using std::get;
    return {{ get<Indices>(std::forward<Tuple>(tuple))... }};
}

template<typename Tuple>
auto to_array(Tuple&& tuple)
-> decltype( to_array(std::declval<Tuple>(), make_indices<Tuple>()) )
{
    return to_array(std::forward<Tuple>(tuple), make_indices<Tuple>());
}


auto main() -> int
{
    using namespace std;

    auto strings = make_tuple( "a"s, "b"s );
    auto more_strings = make_tuple( "c"s, "d"s);
    auto another_string = make_tuple("e"s);

    auto result = to_array(tuple_cat(strings, more_strings, another_string));
    for (const auto& e : result)
    {
        cout << e << endl;
    }
    return 0;
}
Community
  • 1
  • 1
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Note that `build_indices` and `indices` may be replaced by `std::make_index_sequence` and `std::index_sequence`. – Jarod42 Nov 27 '15 at 12:07
1

Here is a function that can join std::arrays.

template<typename T>
auto wrap_value(T&& value)
{
    return std::tuple<T&&>(std::forward<T>(value));
}

template<typename T, std::size_t N>
std::array<T, N>& wrap_value(std::array<T, N>& value)
{
    return value;
}

template<typename T, std::size_t N>
std::array<T, N> const& wrap_value(std::array<T, N> const& value)
{
    return value;
}

template<typename T, std::size_t N>
std::array<T, N>&& wrap_value(std::array<T, N>&& value)
{
    return std::move(value);
}

template<std::size_t... Is, typename... Ts>
std::array<std::common_type_t<Ts...>, sizeof...(Is)>
join_arrays_impl(std::index_sequence<Is...>, std::tuple<Ts...>&& parts)
{
    return {std::get<Is>(std::move(parts))...};
}

template<typename... Ts>
auto join_arrays(Ts&&... parts)
{
    auto wrapped_parts = std::tuple_cat((wrap_value)(std::forward<Ts>(parts))...);
    constexpr auto size = std::tuple_size<decltype(wrapped_parts)>::value;
    std::make_index_sequence<size> seq;
    return (join_arrays_impl)(seq, std::move(wrapped_parts));
}

I don't think you can implement a similar function for std::initializer_lists. See it working here.

Simple
  • 13,992
  • 2
  • 47
  • 47