I was not able to find an answer on how to combine two or more arrays at compiletime in modern c++.
#include <array>
#include <cstdint>
const std::array<std::uint8_t, 1> one_elem = {1};
const std::array<std::uint8_t, 2> two_elem = {2, 3};
const std::array<std::uint8_t, 3> all = {one_elem, two_elem};
// expected: all == {1, 2, 3}
I would be glad with anything that is somewhat easy to read, e.g.
std::uint8_t one_elem[] = {1};
std::uint8_t two_elem[] = {2, 3};
std::uint8_t all[] = {one_elem, two_elem}; // cannot be that hard
Is there a way? What can I do to get this solved?
If you are using C++17, you can do this:
template <typename T, std::size_t N1, std::size_t N2>
constexpr std::array<T, N1 + N2> concat(std::array<T, N1> lhs, std::array<T, N2> rhs)
{
std::array<T, N1 + N2> result{};
std::size_t index = 0;
for (auto& el : lhs) {
result[index] = std::move(el);
++index;
}
for (auto& el : rhs) {
result[index] = std::move(el);
++index;
}
return result;
}
constexpr std::array<std::uint8_t, 1> one_elem = {1};
constexpr std::array<std::uint8_t, 2> two_elem = {2, 3};
constexpr std::array<std::uint8_t, 3> all = concat(one_elem, two_elem);
It doesn't work in C++14, because std::array
isn't constexpr friendly until C++17. However, if you don't care that the final result is constexpr
, you can simply mark each variable as const
, and this will work:
const std::array<std::uint8_t, 1> one_elem = {1};
const std::array<std::uint8_t, 2> two_elem = {2, 3};
const std::array<std::uint8_t, 3> all = concat(one_elem, two_elem);
The compiler will almost certainly optimize the concat
away.
If you need a C++14 solution, we have to create it via std::array
's constructor, so it's not nearly as nice:
#include <array>
#include <cstdint>
#include <cstddef>
#include <type_traits>
// We need to have two parameter packs in order to
// unpack both arrays. The easiest way I could think of for
// doing so is by using a parameter pack on a template class
template <std::size_t... I1s>
struct ConcatHelper
{
template <typename T, std::size_t... I2s>
static constexpr std::array<T, sizeof...(I1s) + sizeof...(I2s)>
concat(std::array<T, sizeof...(I1s)> const& lhs,
std::array<T, sizeof...(I2s)> const& rhs,
std::index_sequence<I2s...>)
{
return { lhs[I1s]... , rhs[I2s]... };
}
};
// Makes it easier to get the correct ConcatHelper if we know a
// std::index_sequence. There is no implementation for this function,
// since we are only getting its type via decltype()
template <std::size_t... I1s>
ConcatHelper<I1s...> get_helper_type(std::index_sequence<I1s...>);
template <typename T, std::size_t N1, std::size_t N2>
constexpr std::array<T, N1 + N2> concat(std::array<T, N1> const& lhs, std::array<T, N2> const& rhs)
{
return decltype(get_helper_type(std::make_index_sequence<N1>{}))::concat(lhs, rhs, std::make_index_sequence<N2>{});
}
constexpr std::array<std::uint8_t, 1> one_elem = {1};
constexpr std::array<std::uint8_t, 2> two_elem = {2, 3};
constexpr std::array<std::uint8_t, 3> all = concat(one_elem, two_elem);
There's already a way to concatenate arrays in C++: std::tuple_cat
. The only problem is that it gives you a tuple<uint8_t, uint8_t, uint8_t>
instead of a std::array<uint8_t, 3>
. But that problem is solvable with a different standard library function: std::apply
. That one is technically C++17, but is implementable in C++14. You just need a funject:
struct to_array_t {
template <class T, class... Ts>
std::array<std::decay_t<T>, sizeof...(Ts)+1> operator()(T&& t, Ts&&... ts) const {
return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}
} to_array{};
and then you can use it:
auto all = std::apply(to_array, std::tuple_cat(one_elem, two_elem));
Which might be easier to just hide behind a function:
template <class Target=void, class... TupleLike>
auto array_concat(TupleLike&&... tuples) {
return std::apply([](auto&& first, auto&&... rest){
using T = std::conditional_t<
!std::is_void<Target>::value, Target, std::decay_t<decltype(first)>>;
return std::array<T, sizeof...(rest)+1>{{
decltype(first)(first), decltype(rest)(rest)...
}};
}, std::tuple_cat(std::forward<TupleLike>(tuples)...));
}
Perfect forwarding with lambdas is a bit ugly. The Target
type is allow the user to specify a type for the resulting array - otherwise it will be selected as the decayed type of the first element.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With