如果通过引用传递,一元和二元运算参数的参数是否保证与原始数据相同?

Are arguments of unary and binary operation arguments guaranteed to be same as original data if passed by reference?

如您所知,我们可以为很多stl函数提供UnaryOperationBinaryOperation。这些方法的参数可以通过值定义,但在很多情况下,我们通过引用传递它们,如下所示:

 Ret fun(const Type &a);                       // UnaryOperation
 Ret fun(const Type1 &a, const Type2 &b);      // BinaryOperation

现在我想知道这些回调参数是否保证与标准的主数据相同。例如,下面的代码在标准中有效吗?

#include <iostream>
#include <vector>
#include <execution>
#include <algorithm>

int main() {
    std::vector<int> arr(10);
    std::transform(std::execution::par, arr.begin(), arr.end(), arr.begin(),
                   [&arr](const int& v) -> int { return (&v - &arr[0]); });
    for (const auto& v: arr)
        std::cout << v << " ";
}

因为当且仅当 v 引用 arr 中的原始元素时 (&v - &arr[0]) 才有效。

所写的示例表现出未定义的行为。在某些情况下,并行算法可以任意复制序列的元素。

[algorithms.parallel.user]/1 Unless otherwise specified, function objects passed into parallel algorithms ... shall not ... rely on the identity of the provided objects.

[algorithms.parallel.exec]/3 Unless otherwise stated, implementations may make arbitrary copies of elements (with type T) from sequences where is_trivially_copy_constructible_v<T> and is_trivially_destructible_v<T> are true. [Note: This implies that user-supplied function objects should not rely on object identity of arguments for such input sequences. Users for whom the object identity of the arguments to these function objects is important should consider using a wrapping iterator that returns a non-copied implementation object such as reference_wrapper<T> (20.14.5) or some equivalent solution. —end note]

严格阅读 [algorithms.parallel.user]/1 表明用户提供的函数绝不能依赖于原始元素而不是副本。 [algorithms.parallel.exec]/3 中的非规范性注释似乎表明,如果元素类型不可简单复制或破坏,那么依赖它是可以的。在任何情况下,该示例使用 int 作为元素类型,它实际上是可复制和可破坏的,并且显然允许 std::transform(par) 实现进行复制。

有关动机,请参阅 P0518r1“允许将副本作为函数对象的参数提供给并行算法以响应 CH11”


另一方面,除非算法规范要求,否则不允许非并行算法复制元素。因此,对于非并行 std::transform,用户提供的函数可能确实依赖于被赋予对源序列元素的引用。

[res.on.data.races]/5 A C++ standard library function shall not access objects indirectly accessible via its arguments or via elements of its container arguments except by invoking functions required by its specification on those container elements.

[alg.transform]std::transform 的规范说它应该为“每个迭代器 i 调用 op(*(first1 + (i - result)))[result, result + N) 范围内”;也就是说,它应该将取消引用迭代器到源序列的结果直接传递给用户提供的函数。