#include <cstddef>
#include <array>
#include <iostream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>

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

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

    auto result = join_arrays(strings, more_strings, another_string);
    for (auto const& i : result) {
        std::cout << i << '\n';
    }

    // Making sure old strings are valid.
    std::cout << '\n';
    std::cout << strings[0] << ' ' << strings[1] << '\n';
    std::cout << more_strings[0] << ' ' << more_strings[1] << '\n';
    std::cout << another_string << '\n';
}
