Чи можу я використовувати 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) в статті cppreference наstd::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]

результат може бути рівним першому у випадку одинарного перетворення [...].

значить операції на місці явно дозволені.

Тепер

[algoritam.parallel.overloads.2]

Якщо не вказано інше, семантика перевантажень алгоритму ExecutionPolicy ідентична їх перевантаженням без.

означає, що політика виконання не має видимих ​​для користувача різниці в алгоритмі. Можна очікувати, що алгоритм дасть такий самий результат, як якщо б ви не вказали політику виконання.


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вектора або erasing, з vectorякого, ймовірно, буде причиною недійсності ітераторів.

Подивіться приклад відмови, який ви НЕ БУДЕТЕ робити наживо .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.