Могу ли я использовать std :: transform вместо политики параллельного выполнения?

11

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

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

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

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

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

Означает ли «изменение каких-либо элементов» действительно то, что я не могу использовать алгоритм на месте, или это говорит о другой детали, которую я неправильно истолковал?

гео
источник

Ответы:

4

Цитировать стандарт здесь

[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]

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

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

Сейчас же

[Algorithms.parallel.overloads.2]

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

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

Timo
источник
6

Я считаю, что речь идет о другой детали. 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самом деле не унарный (то есть он ссылается на другие элементы в последовательности - даже если он только читает их, тогда у вас будет гонка данных).

Маршалл Клоу
источник
1

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

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

Ret fun(const Type &a);

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

Смотрите пример сбоя, который вы НЕ ДОЛЖНЫ делать Live .

забвение
источник