【问题标题】:Initialize std::array by parameter pack from arbitrary index通过参数包从任意索引初始化 std::array
【发布时间】:2016-03-28 15:10:34
【问题描述】:

通过可变模板参数初始化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 的情况下实现相同的功能?

【问题讨论】:

  • @Yakk:现在使用 C++ 的重点是性能。创建临时容器与此相反。它占用了不必要的空间并导致调用不必要的构造函数,可能会根据T 的内容分配大量资源。我看不出他为什么必须证明他不想将参数包转换为某种对象的理由。
  • 在我看来,周一早上对复活节后的人们来说有点难。这个问题很清楚,但我看到 SO 的两个受人尊敬的成员用不相关的陈述来抨击 OP,而不是回答这个问题。
  • @NicolBolas 这些都是很好的理由。但是std::tie 是一个引用容器,其存在和大小(理论上)可以被忽略。它是一个元组。
  • @plasmacel :如果你不知道自己问题的答案(以及为什么要问你是否知道?)那么你不可能知道什么是相关的和不相关的。
  • 在这一点上,如果我们所有人都重新考虑一下我们引导讨论的方式,可能会更好。 :) 不管挑剔,OP点在这一点上已经足够清楚了。关于可能的答案?

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


【解决方案1】:

您可以使用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), ...);
   }

【讨论】:

  • @SergeyA 在 cmets OP 中指定他希望避免运行时成本。
  • @Yakk:使用 c++1z 的表达式折叠,可以删除虚拟数组:((arr[i + Is] = vals), ...)
  • @Yakk 实际上我认为在这种情况下数组的构造 == nop。
  • @YamMarcovic :这完全取决于T 是否可以默认构造。
  • @ildjarn 好的,谢谢。我自己之前的int 例子又跳回了我。
【解决方案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

值得注意的是,这两种情况生成的程序集是等价的。

【讨论】:

    【解决方案3】:
    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)... }}
      {}
      // ...
    };
    

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

    备份一秒,我们知道数组是Ts 的标准布局块。让我们作弊吧。

    // 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&amp;&amp;) 支持! (除非这是您将参数传递给数组的方式,但那是您的问题不是我的)

    我试图在异常密集的环境中进行破坏。我可能听错了。

    NRVO 应该忽略唯一的临时变量(make_arr 的返回值)。另一方面,编译器可能是一个懒惰的人,不会溜进类成员构造函数中。错误的编译器。

    【讨论】:

    • 嗯,第一个版本中值初始化的Ts不符合省略的条件吗?
    • ...yep。所以唯一的实际问题是要求T 可以移动构造,但这将通过保证复制省略来解决。
    • @T.C.我认为, 部分破坏了它?也许不吧?我想我可以写template&lt;size_t I, class T, class...Args&gt; T make(Args&amp;&amp;...args){ return T(std::forward&lt;Args&gt;(args)...); },然后用make&lt;Is, T&gt;()... 替换(Is, void(), T())...。这摆脱了, 操作员滥用,一个加号。更换。 v1 还需要一个编译时已知偏移量,OP 在代码中没有此偏移量,因此仍然需要 v2。
    猜你喜欢
    • 2019-06-24
    • 1970-01-01
    • 2018-01-13
    • 2015-10-05
    • 2012-02-10
    • 2020-01-27
    • 2021-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多