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

时间:2014-07-31 20:37:48

标签: c++ arrays c++11 std constexpr

我从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选项进行编译。

4 个答案:

答案 0 :(得分:28)

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

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

  1. Variadic templates
  2. constexpr expressions
  3. Parameter pack
  4. 首先创建一个模板(空壳)以开始设计递归时尚列表展平功能:

    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)a constexpr overload for the operator[],这意味着你可以编写类似

    的内容
    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]};
    }
    

    (注意aggregate-initialization从一系列整数值初始化对象)

    显然,手动硬编码对两个数组的值的所有索引访问并不比仅仅声明连接数组本身更好。将节省一天的概念如下:Parameter Packs。模板参数包是一个模板参数,它接受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;
    }
    

    所以我们现在唯一需要的是能够编译时生成&#34;索引系列&#34;像

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

    此时我们可以再次利用参数包和继承机制:我们的想法是拥有一个深层嵌套的derived : base : other_base : another_base : ...类层次结构,这些层次结构会累积&#34;索引到参数包中并终止&#34;递归&#34;当索引达到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&lt; 3开始,(无)&gt;并实例化指定的模板,该模板将实例化gen_seq&lt; 2,(无)&gt;作为基类,将实例化gen_seq&lt; 1,(无)&gt;作为其实例化gen_seq&lt; 0的基类,(无)&gt;作为其实例化seq&lt;(nothing)&gt;的基类作为最后的序列。

    序列是&#39;(没有)&#39;,出了点问题..

    为了积累&#34;你需要参数包中的索引&#34;添加副本&#34;每次递归时参数包的索引减少:

    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

答案 1 :(得分:4)

使用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实现就会将任何类似元组转换为数组。)

这是a live example for GCC 4.8,填写了一些尚未支持的C ++ 1y功能。

答案 2 :(得分:2)

另一种方法是使用expression templates。它不会复制数组。

草图:

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

答案 3 :(得分:2)

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

虽然有一个问题,但是一年多以前,一般化的constexpr被投入了标准核心语言,但STL仍然没有更新......

作为实验,打开标题<array>并为非const运算符添加明显缺失的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上运行)