通过任意索引的参数包初始化std :: array

时间:2016-03-28 15:10:34

标签: c++ templates c++11 c++14 variadic-templates

通过可变参数模板参数初始化std::array,从给定索引开始可以通过以下方式完成:

#include <array>

template <typename T, size_t N>
struct A
{
   template <typename ... Ts>
   A(size_t i, Ts ... vals)
   {
      constexpr size_t P = sizeof...(vals);
      std::array<T, P> temp{ vals... };

      for (size_t j = 0; j < P; ++j)
      {
         arr[i + j] = temp[j];
      }
   }

   std::array<T, N> arr;
};

但是有可能在不将参数包转换为临时元组或其他std :: array的情况下实现相同的目的吗?

3 个答案:

答案 0 :(得分:3)

您可以使用std::index_sequence并委派构造函数:

template <typename T, size_t N>
struct A
{
   template <typename ... Ts>
   A(size_t i, Ts&& ... vals)
   :
       A(std::index_sequence_for<Ts...>{}, i, std::forward<Ts>(vals)...)
   {}

   template <std::size_t...Is, typename ... Ts>
   A(std::index_sequence<Is...>, size_t i, Ts&& ... vals)
   {
      int dummy[] = {0, ((arr[i + Is] = vals), void(), 0)...};
      static_cast<void>(dummy); // Avoid warning for unused variable
   }

   std::array<T, N> arr;
};

或者使用C ++ 17的折叠表达式,

   template <std::size_t...Is, typename ... Ts>
   A(std::index_sequence<Is...>, size_t i, Ts&& ... vals)
   {
      (static_cast<void>(arr[i + Is] = vals), ...);
   }

答案 1 :(得分:2)

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;

template<class T, std::size_t=0, class...Args>
T make(Args&&...args){ return T(std::forward<Args>(args)...); }

template <class T, size_t N>
struct A {
  template <std::size_t I, class...Ts>
  A(index_t<I>, Ts&&... vals):
    A(std::make_index_sequence<I>{}, std::forward<Ts>(vals)...)
  {}
  template <std::size_t...Is, class...Ts>
  A(std::index_sequence<Is...>, Ts&&...vals):
    arr{{ make<T,Is>()..., std::forward<Ts>(vals)... }}
  {}
  // ...
};

这需要编译时已知的偏移量,但是避免创建默认构造的数组然后分配给它。然而,它还涉及创建一堆T并将它们移动到阵列中。伊克!

备份第二个,我们知道该数组是T s的标准布局块。让我们作弊。

// stores the raw buffer for the array, and constructs it with
// some care:
template<class T>
using storage = std::aligned_storage_t<sizeof(T),alignof(T)>;
template<class T, size_t N>
struct A_storage {
  storage<std::array<T, N>> raw_arr;
  std::array<T,N>& arr() {
    return *reinterpret_cast<std::array<T, N>*>(&raw_arr);
  }
  std::array<T,N> const& arr() const {
    return *reinterpret_cast<std::array<T, N> const*>(&raw_arr);
  }

  template<class...Ts>
  static void make_arr(storage<std::array<T, N>>& retval, std::size_t offset, Ts&&...ts) {
    auto* ptr = reinterpret_cast<T*>(&retval);
    try {
      std::size_t ctor_count = 0;
      for (size_t i = 0; i < offset; ++i) {
        ++ctor_count;
        ::new((void*)(ptr+i)) T();
      }
      ::new((void*)(ptr +offset)) std::array<T, sizeof...(Ts)>{{
        (++ctor_count, void(), std::forward<Ts>(ts))...
      }};
      for (size_t i = offset+sizeof...(Ts); i < N; ++i) {
        ++ctor_count;
        ::new((void*)(ptr+i)) T();
      }
    } catch(...) {
      // ctor_count is the count of constructors *attempted*
      // so ptr[ctor_count-2] is the last one we *succeeded at*
      // destroy everything we successfully constructed.
      // ctor_count has to be at least 1, as we cannot throw before
      // incrementing.  Let us peel it off.
      --ctor_count;
      // iterate backwards from ctor_count-1 down to 0, so we destroy
      // in reverse order of constructing:
      for (size_t i = 1; i <= ctor_count; ++i) {
        ptr[ctor_count-i].~T();
      }
      throw;
      // I use attempted, because the initializer list syntax of array
      // construction doesn't let me increment after I provide the value
      // for the place I'm constructing.  I can, however, increment before.

    }
  }
  template<class...Ts>
  A_storage(std::size_t offset, Ts&&...ts)
  {
    static_assert(sizeof...(Ts)<=N, "too much data!");
    ASSERT(offset+sizeof...(Ts)<=N);
    make_arr(raw_arr, offset, std::forward<Ts>(ts)...);
  }
  // only runs if the ctor above completes, which means
  // everything was constructed:
  ~A_storage() {
    for (size_t i = 1; i <= N; ++i) {
      arr()[N-i].~T();
    }
  }
};
template<std::size_t N, class T>
struct A:A_storage {
  template<class...Ts>
  A(std::size_t offset, Ts&&...ts):
    A_storage(offset, std::forward<Ts>(ts)...)
  {}
};

一切都是就地建造的。无需T(T&&)支持! (除非你将参数传递给数组,但这不是我的问题)

我试图在异常恶劣的环境中进行破坏。我可能弄错了。

NRVO应该省略唯一的临时变量(make_arr的返回值)。另一方面,编译器可能是一个懒惰而不是忽略类成员构造函数。糟糕的编译器。

答案 2 :(得分:2)

虽然它涉及C ++ 17倍表达式,但受Jarod42评论启发的最简单的解决方案是:

#include <array>

template <typename T, size_t N>
struct A
{
   template <typename ... Ts>
   A(Ts ... vals)
   {
     size_t i = 1; // starting index, can be an argument
     ((arr[i++] = vals) , ...);
   }

   std::array<T, N> arr;
};

int main()
{
   A<int, 4> a(1, 2, 3);

   return 0;
}

使用 Clang 3.6 或更高级别使用优化级别-O1-std=c++1z标记生成的程序集:

A<int, 4ul>::A<int, int, int>(int, int, int):             # @A<int, 4ul>::A<int, int, int>(int, int, int)
pushq   %rbp
pushq   %r15
pushq   %r14
pushq   %rbx
pushq   %rax
movl    %ecx, %r14d
movl    %edx, %r15d
movl    %esi, %ebx
movq    %rdi, %rbp
movl    $1, %esi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %ebx, (%rax)
movl    $2, %esi
movq    %rbp, %rdi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %r15d, (%rax)
movl    $3, %esi
movq    %rbp, %rdi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %r14d, (%rax)
addq    $8, %rsp
popq    %rbx
popq    %r14
popq    %r15
popq    %rbp
retq

这相当于测试用例

template <typename T, size_t N>
struct B
{
   B(T x, T y, T z)
   {
     arr[1] = x;
     arr[2] = y;
     arr[3] = z;
   }

   std::array<T, N> arr;
};

int main()
{
   B<int, 4> b(1, 2, 3);

   return 0;
}

生成的程序集

B<int, 4ul>::B(int, int, int):                     # @B<int, 4ul>::B(int, int, int)
pushq   %rbp
pushq   %r15
pushq   %r14
pushq   %rbx
pushq   %rax
movl    %ecx, %r14d
movl    %edx, %r15d
movl    %esi, %ebx
movq    %rdi, %rbp
movl    $1, %esi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %ebx, (%rax)
movl    $2, %esi
movq    %rbp, %rdi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %r15d, (%rax)
movl    $3, %esi
movq    %rbp, %rdi
callq   std::array<int, 4ul>::operator[](unsigned long)
movl    %r14d, (%rax)
addq    $8, %rsp
popq    %rbx
popq    %r14
popq    %r15
popq    %rbp
retq

值得注意的是,两个案例的生成组合是等效的。