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

问题描述 投票:0回答:5

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

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

c++ arrays c++11 std constexpr
5个回答
35
投票

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

您可以使用三个 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
}

到目前为止一切顺利:

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

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

显然,手动硬编码对两个数组值的所有索引访问并不比仅仅声明连接数组本身更好。拯救世界的概念如下:参数包。模板参数包是接受 0 个或多个模板实参的模板参数。至少具有一个参数包的模板称为 variadic template

最酷的事情是能够将参数包扩展到指定位置,例如:

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

所以我们现在唯一需要的是能够编译时生成“索引系列”,例如

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

此时,我们可以再次利用参数包与继承机制的结合:其想法是拥有一个深度嵌套的

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;

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

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

无论如何都缺少一些东西:上面的代码将从 gen_seq<3, (nothing)> 开始并实例化指定的模板,该模板将实例化 gen_seq<2, (nothing)> 作为其基类,将实例化 gen_seq<1, (nothing)> 作为其基类,将实例化 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,/> .

所以现在我们能够将所有部分重新收集在一起并生成两个索引序列:一个用于第一个数组,一个用于第二个数组,并将它们连接在一起形成一个新的返回数组,该数组将保存两个数组(就像将它们附加在一起)。

此时,以下代码应该很容易理解:

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

http://ideone.com/HeLLDm


参考资料:

https://stackoverflow.com/a/13294458/1938163

http://en.cppreference.com/

http://en.wikipedia.org


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

尽管如此,调用

std::tuple_cat
的结果是一个元组,而不是一个数组。然后就可以将
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> {} );
}

(事实证明,只要元组元素类型兼容,这个

to_array
实现就会将任何类似元组的内容转换为数组。)

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


4
投票

Luc 的帖子回答了这个问题。
但为了好玩,这里有一个没有模板元编程的 C++14 解决方案,只是纯 constexpr。

但是有一个问题,广义 constexpr 一年多前就被选为标准核心语言,但 STL 仍然没有更新......

作为实验,打开标题

<array>
并为非常量运算符[]添加明显缺失的 constexpr

constexpr reference operator[](size_type n);

同时打开

<numeric>
并将std::accumulate转为constexpr函数

template <class InputIterator, class T>
constexpr T accumulate(InputIterator first, InputIterator last, T init);

现在我们可以做:

#include <iostream>
#include <array>
#include <numeric>

template <typename T, size_t... sz>
constexpr auto make_flattened_array(std::array<T, sz>... ar)
{
   constexpr size_t NB_ARRAY = sizeof...(ar);

   T* datas[NB_ARRAY] = {&ar[0]...};
   constexpr size_t lengths[NB_ARRAY] = {ar.size()...};

   constexpr size_t FLATLENGTH = std::accumulate(lengths, lengths + NB_ARRAY, 0);

   std::array<T, FLATLENGTH> flat_a = {0};

   int index = 0;
   for(int i = 0; i < NB_ARRAY; i++)
   {
      for(int j = 0; j < lengths[i]; j++)
      {
         flat_a[index] = datas[i][j];
         index++;
      }
   }

   return flat_a;
}

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

  constexpr auto a = make_flattened_array(a1, a2, a3);

  for(int i = 0; i < a.size(); i++)
     std::cout << a[i] << std::endl;
}

(在 clang trunk 上编译并运行)


2
投票

另一种方法是使用表达式模板。它不复制数组。

草图:

#include <array>

template<class L, class R>
struct AddOp
{
    L const& l_;
    R const& r_;

    typedef typename L::value_type value_type;

    AddOp operator=(AddOp const&) = delete;

    constexpr value_type const& operator[](size_t idx) const {
        return idx < l_.size() ? l_[idx] : r_[idx - l_.size()];
    }

    constexpr std::size_t size() const {
        return l_.size() + r_.size();
    }

    // Implement the rest of std::array<> interface as needed.
};

template<class L, class R>
constexpr AddOp<L, R> make_flattened_array(L const& l, R const& r) {
    return {l, r};
}

constexpr std::array<int, 3> a1 = {1,2,3};
constexpr std::array<int, 2> a2 = {4,5};
constexpr std::array<int, 2> a3 = {6};
constexpr auto a4 = make_flattened_array(a1,a2);
constexpr auto a5 = make_flattened_array(a4,a3);

int main() {
    constexpr auto x = a5[1];
    constexpr auto y = a5[4];
    constexpr auto z = a5[5];
}

0
投票

这是 C++20 的一个小解决方案。


template<class T, std::size_t... SIZE>
constexpr auto concat(const std::array<T, SIZE>... arrays) {
    constexpr std::size_t total_size = (SIZE + ...);

    auto all_elements = std::tuple_cat(arrays...);

    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::array<T, total_size>{{std::get<Is>(all_elements)...}};
    }(std::make_index_sequence<total_size>{});
}
© www.soinside.com 2019 - 2024. All rights reserved.