Могу ли я использовать std :: transform вместо политики параллельного выполнения? - PullRequest
9 голосов
/ 06 октября 2019

Если я не ошибаюсь, я могу заставить std::transform выполнить вместо , используя тот же диапазон, что и итератор ввода и вывода. Предположим, у меня есть некоторый std::vector объект vec, тогда я бы написал

std::transform(vec.cbegin(),vec.cend(),vec.begin(),unary_op)

, используя подходящую унарную операцию unary_op.

Используя стандарт C ++ 17, я быхотел бы выполнить преобразование параллельно, вставив туда std::execution::par в качестве первого аргумента. Это заставит функцию перейти от перегрузки (1) к (2) в статье cppreference на std::transform. Однако в комментариях к этой перегрузке говорится:

unary_op [...] не должно делать недействительными какие-либо итераторы, включая конечные итераторы, или изменять любые элементы задействованных диапазонов. (начиная с C ++ 11)

Действительно ли "изменить какие-либо элементы" означает, что я не могу использовать алгоритм на месте, или это говорит о другой детали, которую я неправильно истолковал?

Ответы [ 3 ]

6 голосов
/ 06 октября 2019

Я считаю, что речь идет о другой детали. unary_op принимает элемент последовательности и возвращает значение. Это значение сохраняется (transform) в последовательности назначения.

Так что это unary_op было бы хорошо:

int times2(int v) { return 2*v; }

, но этого не будет:

int times2(int &v) { return v*=2; }

Но это не совсем то, о чем вы спрашиваете. Вы хотите знать, можете ли вы использовать unary_op версию transform в качестве параллельного алгоритма с тем же исходным и целевым диапазоном. Я не понимаю, почему нет. transform отображает один элемент исходной последовательности в один элемент целевой последовательности. Однако, если ваш unary_op на самом деле не является унарным (то есть он ссылается на другие элементы в последовательности - даже если он только читает их, тогда у вас будет гонка данных).

4 голосов
/ 06 октября 2019

Здесь для цитирования стандарта

[alg.transform.1]

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
}

Независимо от UnaryOperation у нас есть

[alg.transform.5]

результат может быть равен первому в случае унарного преобразования [...].

означает, что операции на месте явно разрешены.

Теперь

[gorithms.parallel.overloads.2]

Если не указано иноеуказанная семантика перегрузок алгоритма ExecutionPolicy идентична перегрузкам без них.

означает, что политика выполнения не имеет видимой для пользователя разницы в алгоритме. Вы можете ожидать, что алгоритм даст такой же результат, как если бы вы не указывали политику выполнения.

1 голос
/ 06 октября 2019

Как вы можете видеть в примере ссылки, которую вы цитировали, изменение любых элементов не означает все типы изменения элементов:

Подписьфункция должна быть эквивалентна следующей:

Ret fun(const Type &a);

Это включает в себя модификацию элементов. В худшем случае, если вы используете тот же итератор для пункта назначения, модификация не должна вызывать аннулирование итераторов, например, push_back для вектора или eras от vector, что, вероятно, приведет к аннулированию итераторов.

См. Пример сбоя, который НЕ СЛЕДУЕТ делать Live .

...