【问题标题】:Using mutating function in std::accumulate在 std::accumulate 中使用变异函数
【发布时间】:2014-11-07 16:16:32
【问题描述】:

有些情况

x += y;

效率高很多
x = x + y;

(假设xy 属于某些具有复杂重载运算符的类类型)。现在我当然可以折叠

X x;
BOOST_FOREACH(Y const &y, collection)
    x += y;

(我被困在使用旧编译器的平台上)但不知何故我更喜欢std::accumulate。是否可以(或其他算法,但不是 for_each)改为使用+=

std::accumulate 应该像这样调用运算符

sum = op(sum, *iter);

但是可以依赖它,也就是会

X x(std::accumulate(collection.begin(),
                    collection.end(),
                    X(),
                    std::mem_fun_ref(&X::operator+=)));

工作?

【问题讨论】:

  • std::for_each 有什么问题?
  • 是的,您可以信赖它。你可以做一个非常丑陋的黑客。不要。
  • @Rapptz:它不再提供任何优于循环的优势,无论是代码长度还是抽象量。
  • 你不能使用std::accumulate,因为它要求不修改范围内的任何元素,而+=很明显会修改一个元素。
  • @BillLynch:不在范围内。

标签: c++ language-lawyer c++03 c++-standard-library


【解决方案1】:

就您的保证而言..

C++03 [lib.accumulate]:

要求:T 必须满足 CopyConstructible (20.1.3) 和 Assignable (23.1) 类型的要求。 binary_op 不会产生副作用。

所以这个函数不能有任何“副作用”。其定义如下:

C++03 [intro.execution]:

访问由 volatile 左值 (3.10) 指定的对象、修改对象、调用库 I/O 函数或调用执行任何这些操作的函数都是副作用,这是执行环境状态的变化。

强调我的。

请注意,在 C++11 中,措辞略有变化(第 26.7.2 节):

要求:T 应满足 CopyConstructible(表 21)和 CopyAssignable(表 23)类型的要求。在 [first,last] 范围内,binary_op 既不能修改元素,也不能使迭代器或子范围无效。

但是,您可以创建一种标签类型,该类型适用于您扔给它的任何东西,并且是一个空操作..

struct tag {
    template<typename T>
    tag& operator=(const T&) { return *this; } // not explicitly necessary
};

然后你创建一个函子,在其左侧接受该标记类型,并使用一个闭包来维持结果。

struct accumulator {
    int& x;
    accumulator(int& x): x(x) {}
    tag operator()(tag t, int y) {
        x += y;
        return t;
    }
};

然后正常使用..

std::accumulate(v.begin(), v.end(), tag(), accumulator(x));

它应该可以工作。虽然我认为此时 std::for_each 是更好的选择,或者可能是 BOOST_FOREACH 宏。

您也可以编写自己的算法,这样可以减少样板:

template<typename It, typename T, typename Op>
T inplace_accumulate(It begin, It end, T init, Op op) {
    for(; begin != end; ++begin) {
        op(init, *begin);
    }
    return init;
}

然后,您只需使用此签名将常规累加器提供给它:

struct accumulator {
    template<typename T>
    void operator()(T& x, const T& y) const {
        x += y;
    }
};

// somewhere else
int x = inplace_accumulate(v.begin(), v.end(), 10, accumulator());

【讨论】:

  • 此时BOOST_FOREACH 是更好的选择。没意思。
  • @JanHudec 好吧。另一种选择是编写自己的算法。
【解决方案2】:

是的,您可以信赖它。

您可以编写自己的真正ugly 运算符,忽略总和,并希望编译器会优化您创建的垃圾。

请不要。我怀疑是否有人愿意阅读该代码。

你为什么不推出自己的算法?

template<class InputIt, class T>
T accumulate_fast(InputIt first, InputIt last, T init)
{
    for (; first != last; ++first) {
        init += *first;
    }
    return init;
}

这是通用的,不需要为每种类型使用单独的自定义二元运算符。

【讨论】:

  • 这个答案不一致。请选择。要么我可以依赖“它”(= op 称为sum = op(sum, *iter)),在这种情况下,我不必将我的算法作为std::accumulate 与@ 一起推出987654324@ 会起作用,或者我应该推出自己的算法,在这种情况下,原因是我不能依赖“它”。
  • 没有不一致的地方。你可以依赖它,只是它是一个丑陋的黑客。我没有说你必须推出你自己的算法。但在我看来,将您自己的结果滚动到更清晰的代码中。
  • 你读过“它”是什么吗?显然不是。您提到的黑客不使用“它”。依靠“它”不需要那个 hack。
  • @JanHudec:我读过它(再次,只是为了确定),非常感谢......我不知道你在说什么。正如我所说,您可以依靠它。如果您对此有疑问,请反驳它。这是我目前能做的最好的事情,因为正如我所说,我不知道你在说什么。
  • 我已经编辑了这个问题,以便更清楚地说明我在说什么。自从添加了相关的标准报价以来,我已经接受了另一个答案(不,我不能依赖它,或者至少不能滥用它)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2019-02-09
  • 2022-10-25
相关资源
最近更新 更多