c ++ 11 constexpr将std :: array的列表展平为数组

use*_*188 19 c++ arrays std constexpr c++11

我开始使用c ++ 11,constexpr和模板元编程似乎是一种在微型微控制器上保存稀缺内存的好方法.

有没有办法编写模板来展平constexpr数组列表,我需要的是一种方法:

constexpr std::array<int, 3> a1 = {1,2,3};
constexpr std::array<int, 2> a2 = {4,5};
constexpr auto a3 = make_flattened_array (a1,a2);
Run Code Online (Sandbox Code Playgroud)

我使用gcc 4.8.4(arm-none-eabi),如果需要,可以使用std = c ++ 11或c ++ 1y选项进行编译.

Mar*_* A. 29

注意 - 我理解你的问题如下:你想要加入这两个数组并将结果展平为一个包含其元素串联的新数组.

您可以使用三个C++ 11 +概念来实现目标:

  1. 变种模板
  2. constexpr表达式
  3. 参数包

首先创建一个模板(一个空壳)来开始设计递归时尚列表展平函数:

template<unsigned N1, unsigned N2>
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  // TODO
}
Run Code Online (Sandbox Code Playgroud)

到目前为止这么好:说明constexpr符将提示编译器每次编译时都能评估该函数.

现在对于有趣的部分:std :: array有(自c ++ 1y)运算符[]constexpr重载,这意味着你可以写类似的东西

template<unsigned N1, unsigned N2>
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  return std::array<int,N1+N2>{a1[0],a1[1],a1[2],a2[0],a2[1]};
}
Run Code Online (Sandbox Code Playgroud)

(注意聚合初始化以从一系列整数值初始化对象)

显然,手动硬编码对两个数组的值的所有索引访问并不比仅仅声明连接数组本身更好.将节省一天的概念如下:参数包.模板参数包是一个模板参数,它接受0个或更多模板参数.具有至少一个参数包的模板称为可变参数模板.

很酷的是将参数包扩展到指定位置的能力,例如:

#include <iostream>
#include <array>

template<unsigned... Num>
std::array<int, 5> function(const std::array<int,5>& source) {
    return std::array<int,5>{source[Num]...};
}


int main() {
    std::array<int,5> source{7,8,9,10,11};
    std::array<int,5> res = function<0,1,2,3,4>(source);

    for(int i=0; i<res.size(); ++i)
        std::cout << res[i] << " "; // 7 8 9 10 11

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以我们现在唯一需要的是能够编译时生成"索引系列"之类的

std::array<int,5> res = function<0,1,2,3,4>(source);
                                 ^ ^ ^ ^ ^
Run Code Online (Sandbox Code Playgroud)

在这一点上,我们可以再次利用参数包和继承机制:想法是有一个深层嵌套的derived : base : other_base : another_base : ...类层次结构,它将索引"累积"到参数包中,并在索引时终止"递归"达到0.如果您不理解上一句话,请不要担心并看一下以下示例:

std::array<int, 3> a1{42,26,77};

// goal: having "Is" = {0,1,2} i.e. a1's valid indices
template<unsigned... Is> struct seq;
Run Code Online (Sandbox Code Playgroud)

我们可以通过以下方式生成一系列索引:

template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, Is...>{}; // each time decrement the index and go on
template<unsigned... Is>
struct gen_seq<0 /*stops the recursion*/, Is...> : /* generate the sequence */seq<Is...>{};

std::array<int, 3> a1{42,26,77};
gen_seq<3>{};
Run Code Online (Sandbox Code Playgroud)

无论如何都有一些缺失:上面的代码将以gen_seq <3,(nothing)>开始,并实例化指定的模板,该模板将实例化gen_seq <2,(nothing)>作为其实例化gen_seq <1的基类,(无) )>作为其基类,将实例化gen_seq <0,(nothing)>作为其基类,将seq <(nothing)>实例化为最终序列.

顺序是'(没什么)',有些事情是错的..

为了将索引"累积"到参数包中,您需要在每次递归时将减少索引的"副本"添加到参数包中:

template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, /*This copy goes into the parameter pack*/ N-1, Is...>{};

template<unsigned... Is>
struct gen_seq<0 /*Stops the recursion*/, Is...> : /*Generate the sequence*/seq<Is...>{};
template<unsigned... Is> struct seq{};

// Using '/' to denote (nothing)
gen_seq<3,/> : gen_seq<2, 2,/> : gen_seq<1,  1,2,/> : gen_seq<0, 0,1,2,/> : seq<0,1,2,/> .
Run Code Online (Sandbox Code Playgroud)

所以现在我们能够重新收集所有碎片并生成两个索引序列:一个用于第一个数组,一个用于第二个数组,并将它们连接在一起成为一个新的返回数组,它将保持两个连接和扁平的并集数组(如将它们附加在一起).

在这一点上,以下代码应该易于理解:

#include <iostream>
#include <array>

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
// Expansion pack
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2, seq<I1...>, seq<I2...>){
  return { a1[I1]..., a2[I2]... };
}

template<unsigned N1, unsigned N2>
// Initializer for the recursion
constexpr std::array<int, N1+N2> concat(const std::array<int, N1>& a1, const std::array<int, N2>& a2){
  return concat(a1, a2, gen_seq<N1>{}, gen_seq<N2>{});
}

int main() {
    constexpr std::array<int, 3> a1 = {1,2,3};
    constexpr std::array<int, 2> a2 = {4,5};

    constexpr std::array<int,5> res = concat(a1,a2);
    for(int i=0; i<res.size(); ++i)
        std::cout << res[i] << " "; // 1 2 3 4 5

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

http://ideone.com/HeLLDm


参考文献:

/sf/answers/930612091/

http://en.cppreference.com/

http://en.wikipedia.org

  • 非常感谢你的回答!我终于理解了包装序列,在阅读了我能找到的所有参考文献之后仍然在我脑海中.问题虽然:为什么我们同时使用'I1`和`I2`?我们不能重复使用同一个包,因为两个扩展都是独立的吗? (2认同)
  • 好吧,还没有看到`+`:)在C++ 1y中,无论如何我们都会在StdLib中使用`integer_sequence`等,我们可以使用循环. (2认同)

Luc*_*ton 5

随着C ++ 1Y,实现可以(但不是必需的),让std::tuple_cat工作与任何元组样的类型,而不是只std::tuple<T...>。就我们而言,std::array<T, N>就是这种类型。因此我们可以尝试:

constexpr std::array<int, 3> a1 = {1, 2, 3};
constexpr std::array<int, 2> a2 = {4, 5};
constexpr auto a3 = std::tuple_cat(a1, a2);
// note:

// not possible
// constexpr auto e = a3[3]

// instead
constexpr auto e = std::get<3>(a3);
Run Code Online (Sandbox Code Playgroud)

碰巧的是,对的调用结果std::tuple_cat是一个元组,而不是数组。然后可以将an std::tuple<T, T,… , T>变成std::array<T, N>

template<
    typename Tuple,
    typename VTuple = std::remove_reference_t<Tuple>,
    std::size_t... Indices
>
constexpr std::array<
    std::common_type_t<std::tuple_element_t<Indices, VTuple>...>,
    sizeof...(Indices)
>
to_array(Tuple&& tuple, std::index_sequence<Indices...>)
{
    return { std::get<Indices>(std::forward<Tuple>(tuple))... };
}

template<typename Tuple, typename VTuple = std::remove_reference_t<Tuple>>
constexpr decltype(auto) to_array(Tuple&& tuple)
{
    return to_array(
        std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size<VTuple>::value> {} );
}
Run Code Online (Sandbox Code Playgroud)

(事实证明to_array,只要元组元素类型兼容,该实现就可以将任何类似元组的数组转换为数组。)

这是GCC 4.8的实时示例,其中填充了一些尚不支持的C ++ 1y功能。