【问题标题】:Enumerating over a fold expression枚举折叠表达式
【发布时间】:2019-01-19 15:08:05
【问题描述】:

我有一些辅助代码使用编译时索引执行向量重新洗牌。最重要的是生成的代码尽可能高效。我依赖于带有折叠表达式的参数包,我想知道编写此类代码的最佳实践是什么。

一个实际的例子:假设有一个函数insert 将容器y 的元素插入容器x 的位置Ii,其中位置是编译时常量。这个函数的基本签名是这样的:

template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y);

它的调用方式如下:insert&lt;0, 2&gt;(x, y)。我看到了两种明显的实现可能性。

首先:使用辅助索引变量迭代y

template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y) {
  int i = 0;
  ((x[Ii] = y[i++]), ...);
  return x;
}

我对这个解决方案的问题是变量i:我必须依靠编译器来优化它。

第二种解决方案避免了任何运行时依赖,但它需要一个辅助函数,使得整个实现相当难看:

template<size_t... Ii, size_t... Yi, size_t Xsize, size_t Size>
constexpr container<Xsize> insert_(container<Xsize> x, container<Ysize> y, std::index_sequence<Yi...>) {
  ((x[Ii] = y[Yi]), ...);
  return x;
}

template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y) {
  return insert_<Ii...>(x,y, std::make_index_sequence<sizeof...(Ii)> {});
}

有没有办法避免运行时变量和辅助函数?

【问题讨论】:

  • std::make_index_sequence&lt;&gt; 基本上是为这样的东西设计的。即使它需要一个辅助功能,我喜欢你的第二个解决方案。如果没有另一个函数来接收它,你就不能真正使用索引序列。
  • 你有什么理由认为编译器不会优化运行时变量吗?诚然,这很尴尬,但如果它完成了工作......?
  • 例如在 -O1 有 nothing
  • @Barry,是的,编译器非常擅长优化它。但有时它仍然不完美。问题是,这是用于 SIMD 处理的,无论如何,这些调用中的大多数都应该编译成一两个 SIMD 指令,我正在努力解决一些可能会阻止优化器发挥最佳工作的问题。

标签: c++ c++17 variadic-templates template-meta-programming fold-expression


【解决方案1】:

最重要的是生成的代码尽可能高效。

关于您的示例的旁注:您应该确保性能不会因按值传递函数参数而受到影响。返回值也一样。

有没有办法避免运行时变量和辅助函数?

您可以实现可重用的辅助函数。例如,考虑以下代码。

static_assert(__cplusplus >= 201703L, "example written for C++17 or later");

#include <cstddef>

#include <array>
#include <type_traits>
#include <utility>

namespace detail {

template<std::size_t... inds, class F>
constexpr void gen_inds_impl(std::index_sequence<inds...>, F&& f) {
  f(std::integral_constant<std::size_t, inds>{}...);
}

}// detail

template<std::size_t N, class F>
constexpr void gen_inds(F&& f) {
  detail::gen_inds_impl(std::make_index_sequence<N>{}, std::forward<F>(f));
}

// the code above is reusable

template<
  std::size_t... inds_out,
  class T, std::size_t size_out, std::size_t size_in
>
constexpr std::array<T, size_out> insert1(
  std::array<T, size_out> out,
  std::array<T, size_in> in
) {
  static_assert((... && (inds_out < size_out)));
  static_assert(sizeof...(inds_out) <= size_in);

  gen_inds<sizeof...(inds_out)>([&] (auto... inds_in) {
    ((out[inds_out] = in[inds_in]), ...);
  });

  return out;
}

类似的替代方法是static_for 方法:

static_assert(__cplusplus >= 201703L, "example written for C++17 or later");

#include <cstddef>

#include <array>
#include <type_traits>
#include <utility>

namespace detail {

template<std::size_t... inds, class F>
constexpr void static_for_impl(std::index_sequence<inds...>, F&& f) {
  (f(std::integral_constant<std::size_t, inds>{}), ...);
}

}// detail

template<std::size_t N, class F>
constexpr void static_for(F&& f) {
  detail::static_for_impl(std::make_index_sequence<N>{}, std::forward<F>(f));
}

// the code above is reusable

template<
  std::size_t... inds_out,
  class T, std::size_t size_out, std::size_t size_in
>
constexpr std::array<T, size_out> insert2(
  std::array<T, size_out> out,
  std::array<T, size_in> in
) {
  static_assert(sizeof...(inds_out) >= 1);

  static_assert((... && (inds_out < size_out)));
  static_assert(sizeof...(inds_out) <= size_in);

  constexpr std::size_t N = sizeof...(inds_out);

  static_for<N>([&] (auto n) {
    // note the constexpr
    constexpr std::size_t ind_out = std::array{inds_out...}[n];
    constexpr std::size_t ind_in = n;
    out[ind_out] = in[ind_in];
  });

  return out;
}

【讨论】:

  • 我将研究基于 labda 的方法,它可能会解决我看到的其他一些设计问题。至于按值传递:所有函数都显式声明为引用透明,并将被强制内联。该领域是 SIMD 编程,其想法是无论如何调用这些函数都会被一两条 CPU 指令取代。
【解决方案2】:

我认为避免运行时变量和辅助函数是不可能的(希望有人可以反驳这一点)。

我非常喜欢您的第二个解决方案,但是...为y 使用迭代器怎么样(如果y 显然支持cbegin() 和迭代器)。

类似(注意:代码未测试)

template <std::size_t Ii...., std::size_t Xsize, std::size_t Ysize>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> const & y) {
   auto it = y.cbegin();
   ((x[Ii] = *it++), ...);
   return x;
}

这几乎是您的第一个解决方案,但访问y 递增迭代器应该(我想,对于顺序遍历,对于某些容器)比使用operator[]() 更有效(更有效一点)。

但我也认为,使用好的优化器不会有明显的差异。

【讨论】:

    猜你喜欢
    • 2023-03-24
    • 1970-01-01
    • 2021-10-20
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 2021-05-13
    相关资源
    最近更新 更多