【问题标题】:Can algorithms be made compatible with expression templates?算法可以与表达式模板兼容吗?
【发布时间】:2015-11-25 10:09:38
【问题描述】:

假设我有一些基于数组的代码可以被表达式模板使用。例如,我为这些数组重载了 operator[],并且还重载了算术运算符 + 等。

现在我想让 STL 算法any_of 在这样的数组上运行。简单的方法就是这样做

ExprArray<double, N> b, c; // init etc. 
auto a = b + c;            // for (auto i = 0; i < N; ++i) { a[i] = b[i] + c[i]; }
auto res = std::any_of(begin(a), end(a), SomePred{});

当然,我希望能够缩短计算并有一个经过修改的(基于范围的)lib::any_of

// only compute b[i] + c[i] until SomePred is satisified
auto res = lib::any_of(b + c, SomePred{}); // write as explicit loop over b[i] + c[i]

在其输入上以operator[] 形式写入lib::any_of 将完成这项工作,就像对重载的operator+ 所做的一样。但是,这需要对我可能在此类数组上运行的所有 STL 算法进行类似的重新实现。

问题:假设我想通过仅修改 ExprArray iterators 来重用现有的基于范围的算法(Boost.Range、range-v3)。是否可以修改 ExprArray 迭代器 operator*operator++,使其对基于范围的算法透明?

// only compute b[i] + c[i] until SomePred is satisified
// needs to eventually dispatch to 
// for (auto i = 0; i < N; ++i)
//     if (SomePred(b[i] + c[i])) return true;
// return false;
auto res = ranges::any_of(b + c, SomePred{});

所以如果算法版本实际上是根据迭代器实现的,循环for (auto it = first; it != last; ++it) 需要*it 知道它需要计算b[i] + c[i] 的事实,而++it 必须知道它需要做++i.

【问题讨论】:

  • 当谓词为真时停止对于std::any_of 来说应该是一件很自然的事情,您是否检查过它是否已经这样做了? (你可以通过传递一个打印它作为谓词接收的值的 lambda 来做到这一点。)
  • @JoachimPileborg 停止并不是真正的问题,它是表达式模板及其开始/结束迭代器的惰性计算。如果我只重载了operator[],我需要一个基于迭代器的实现来了解它接收表达式模板并将*it 转换为适当的expr[i] 的事实。问题是,迭代器的operator* 可以为我做那种工作吗?
  • @TemplateRex 请注意,问题是关于算法 c++ 库而不是算法设计,所以算法标签不合适
  • 既然你是编写迭代器类的人(我假设),你可以让它做任何你需要的事情。
  • @TemplateRex 不是算法的实现,而是迭代器类别的要求,我想说。也就是说,根据类别,迭代器上允许使用哪些表达式。

标签: c++ expression-templates range-v3


【解决方案1】:

这个问题似乎简化为“我可以为我的表达式模板实现迭代器吗?”我认为这很简单。假设“表达式模板”知道它们的size 并重载了operator[],迭代器只需要保存对表达式对象的引用和它所代表的范围内的偏移量:

template <class Expr>
class iterator {
public:
  using iterator_category = ranges::random_access_iterator_tag;
  using difference_type = std::ptrdiff_t;
  using value_type = typename Expr::value_type;

  iterator() = default;
  constexpr iterator(Expr& e, difference_type i) :
    expr_{&e}, i_{i} {}

  constexpr bool operator==(const iterator& that) const {
    return assert(expr_ == that.expr_), i_ == that.i_;
  }
  constexpr bool operator!=(const iterator& that) const {
    return !(*this == that);
  }
  // Similarly for operators <, >, <=, >=

  value_type operator*() const {
    return (*expr_)[i_];
  }
  value_type operator[](difference_type n) const {
    return (*expr_)[i_ + n];

  iterator& operator++() & { ++i_; }
  iterator operator++(int) & { auto tmp = *this; ++*this; return tmp; }
  // Similarly for operator--

  iterator operator+(difference_type n) const {
    return iterator{expr_, i_ + n};
  }
  // Similarly for operators -, +=, and -=

  friend iterator operator+(difference_type n, const iterator& i) {
    return i + n;
  }

private:
    Expr* expr_;
    difference_type i_;
};

现在您只需为“表达式模板”安排返回iterator{*this, 0}iterator{*this, size()}beginend 成员。

【讨论】:

  • 谢谢,它甚至适用于 STL 两腿迭代器算法,因为 end() 不必访问 operator[] 并且可以简单地使用数组大小​​ N。我担心我必须为beginend 计算两次表达式,但是范围包装器只会存储表达式模板并调度两个迭代器。
【解决方案2】:

这里的问题是b+c 返回什么。如果它返回一个真正的ExprArray,你就不能真正进行惰性评估。数组需要填充。您不能存储对 bc 的引用,因为您不知道它们的生命周期。

但是,如果它返回一个LazyAddition,其conversionExprArray 执行加法,那么很容易看出LazyAddition::iterator 也可以实现延迟加法。这里的风险是auto a = b+c - 这将创建一个带有待处理引用的LazyAddition 对象,而不是ExprArray 对象。

如果您尝试在幕后将ExprArray 实现为智能指针,事情会变得非常糟糕。当然,您可以实现 Copy-On-Write,以便 b+c 是一个 ExprArray,它保留指向两个原始数组的指针。但是一旦你调用T&amp; ExprArray&lt;T&gt;::operator[],COW 就会启动,并将 整个 数组复制到单个元素 read 上! (const 上的 C++ 运算符重载规则不适用于 operator[],当参数本身为 const 时选择 const 版本,而不是用于读取访问时)

【讨论】:

  • 关于生命周期的问题:调用ranges::any_of(b+c, Pred{}) 时,只需假设bc 在算法运行期间是存活的。
  • “假设”是我说存在风险的原因。您无法区分b+cb+c,即使一个出现在ranges::any_of(b+c, Pred{}) 中,另一个出现在auto a = b+c 中。由您决定风险是否可以接受。
  • 我正在使用 Vandevoorde & Josuttis 的示例(他们的模板书中的第 18 章),其中operator= 复制了右侧的所有元素。该示例中的表达式模板都通过引用存储 lhs 和 rhs 参数,以便懒惰地计算整个表达式树。所以b+c 是一个惰性表达式,访问operator[] 只会计算一个元素,除非当然有别名并且cb 的移位版本。
  • 顺便说一句,在 V&J 示例中,他们有一个 template&lt;class Rep=RawArray&gt; ExprArray 模板,其中 Rep 确实可以是 LazyAddition 类。所以b+c和类似的表达式真的都是ExprArrays,只要你在将表达式传递给算法时保持在本地范围内,就不会有悬空引用的危险。
猜你喜欢
  • 1970-01-01
  • 2019-09-27
  • 2012-03-30
  • 1970-01-01
  • 2014-04-28
  • 2013-08-04
  • 2017-12-18
  • 2021-02-13
  • 2015-12-02
相关资源
最近更新 更多