【问题标题】:Combine two or more arrays of different size to one array at compiletime在编译时将两个或多个不同大小的数组合并为一个数组
【发布时间】:2017-07-24 18:04:02
【问题描述】:

我无法找到有关如何在现代 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}

我会很高兴有一些容易阅读的东西,例如

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

有办法吗?我该怎么做才能解决这个问题?

【问题讨论】:

    标签: c++ arrays c++14


    【解决方案1】:

    如果你使用的是 C++17,你可以这样做:

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

    它在 C++14 中不起作用,因为 std::array 在 C++17 之前对 constexpr 不友好。但是,如果您不在乎最终结果是constexpr,您可以简单地将每个变量标记为const,这样就可以了:

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

    编译器几乎肯定会优化掉concat

    如果您需要 C++14 解决方案,我们必须通过 std::array 的构造函数来创建它,所以它不是那么好:

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

    【讨论】:

    • 也许这是可能的。你有什么建议让它适合两个以上的数组?
    • 请注意3 可以是one_elem.size() + two_elem.size(),因为array::sizeconstexpr
    • @vsoftco 您可以随时将其声明为constexpr auto all = ...
    • 您也可以不使用constexpr,第一个解决方案在 C++14 中运行良好。您没有在问题中使用constexpr
    • @prehistoricpenguin 我们无法向现有类型添加方法。但是你可以写concat(one_elem, two_elem)
    【解决方案2】:

    已经有一种在 C++ 中连接数组的方法:std::tuple_cat。唯一的问题是它给你一个tuple&lt;uint8_t, uint8_t, uint8_t&gt; 而不是std::array&lt;uint8_t, 3&gt;。但是这个问题可以通过不同的标准库函数来解决:std::apply。那个在技术上是 C++17,但可以在 C++14 中实现。你只需要一个 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{};
    

    然后你就可以使用它了:

    auto all = std::apply(to_array, std::tuple_cat(one_elem, two_elem));
    

    隐藏在函数后面可能更容易:

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

    使用 lambda 进行完美转发有点难看。 Target 类型允许用户为结果数组指定类型 - 否则它将被选为第一个元素的衰减类型。

    【讨论】:

    • 这种方法的一个真正强大的好处是它是通用的:它不仅仅适用于std::array。你可以array_concat 一个tuple&lt;T, T, T&gt; 和一个array&lt;T, 5&gt; 就好了。它也很容易变参,因为它使用std::tuple_cat
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-16
    • 2013-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多