如果没记错的话,可以通过使用与输入和输出迭代器相同的范围来使std::transform
执行就地。假设我有一些std::vector
对象vec
,那么我会写
std::transform(vec.cbegin(),vec.cend(),vec.begin(),unary_op)
使用适当的一元运算unary_op
。
使用C ++ 17标准,我想通过在其中粘贴std::execution::par
作为第一个参数来并行执行转换。这会使函数从cppreference article on std::transform
中的重载(1)变为(2)。但是,对此超载的注释说:
std::transform
[...]不得使任何迭代器(包括最终迭代器)无效,或修改所涉及范围的任何元素。 (自C ++ 11起)
“修饰任何元素”确实意味着我无法就地使用算法,还是在谈论我误解的其他细节?
谢谢。
我相信这是在谈论一个不同的细节。 unary_op
接受序列的一个元素并返回一个值。该值(通过unary_op
)存储到目标序列中。
所以此transform
很好:
unary_op
但是这个不会:
int times2(int v) { return 2*v; }
但这不是您真正要问的。您想知道是否可以将int times2(int &v) { return v*=2; }
的unary_op
版本用作具有相同源和目标范围的并行算法。我不明白为什么不这样。 transform
将源序列的单个元素映射到目标序列的单个元素。但是,如果您的transform
并不是真正的一元代码,(即它引用了序列中的其他元素-即使它仅读取它们,那么您将进行数据竞争)。
在此处引用标准
[alg.transform.1]
op [...]不得使迭代器或子范围无效,也不得修改范围
这禁止您的unary_op
修改作为参数给出的值或容器本身。
unary_op
但是,可以的。
auto unary_op = [](auto& value)
{
value = 10; // this is bad
return value;
}
auto unary_op = [&vec](auto const& value)
{
vec[0] = value; // also bad
return value;
}
auto unary_op = [&vec](auto& value)
{
vec.erase(vec.begin()); // nope
return value;
}
独立于auto unary_op = [](auto& value) // const/ref not strictly needed
{
return value + 10; // totally fine
}
auto unary_op = [&vec](auto& value)
{
return value + vec[0]; // ok in sequential but not in parallel execution
}
我们有
[alg.transform.5]
如果是一元变换,结果可能等于第一个。
意味着明确允许就地操作。
现在
[algorithms.parallel.overloads.2]
除非另有说明,否则ExecutionPolicy算法重载的语义与其没有时的重载相同。
表示执行策略在算法上没有用户可见的区别。您可以期望该算法产生与未指定执行策略完全相同的结果。
您可以在引用的链接的示例中看到,修改任何元素没有的确意味着对元素进行了所有类型的修改:
该函数的签名应等同于以下内容:
UnaryOperation
其中包括对元素的修改。在最坏的情况下,如果将相同的迭代器用作目标,则修改不会导致迭代器无效,例如Ret fun(const Type &a);
到push_back
的向量或eras
的转换,这可能会导致迭代器无效。
查看您不应该执行的失败示例vector
。