Czy mogę używać std :: transform w miejscu z równoległą polityką wykonywania?

11

Jeśli się nie mylę, mogę wykonać std::transformdziałanie w miejscu , używając tego samego zakresu co iterator wejściowy i wyjściowy. Załóżmy, że mam jakiś std::vectorprzedmiot vec, a potem napiszę

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

przy użyciu odpowiedniej operacji jednoargumentowej unary_op.

Korzystając ze standardu C ++ 17, chciałbym wykonać transformację równolegle, wstawiając std::execution::partam jako pierwszy argument. Spowodowałoby to przejście funkcji z przeciążenia (1) na (2) w artykule o preferencjach cpstd::transform . Jednak komentarze do tego przeciążenia mówią:

unary_op[...] nie może unieważniać żadnych iteratorów, w tym iteratorów końcowych, ani modyfikować żadnych elementów zaangażowanych zakresów. (od C ++ 11)

Czy „modyfikowanie jakichkolwiek elementów” naprawdę oznacza, że ​​nie mogę użyć algorytmu na miejscu, czy chodzi tu o inny szczegół, który źle zinterpretowałem?

geo
źródło

Odpowiedzi:

4

Aby zacytować standard tutaj

[alg.transform.1]

op [...] nie unieważnia iteratorów lub podzakresów ani nie modyfikuje elementów w zakresach

zabrania unary_opto modyfikowania wartości podanej jako argument lub samego kontenera.

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;
}

Jednak obserwowanie jest w porządku.

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
}

Niezależny od UnaryOperationnas

[alg.transform.5]

wynik może być równy pierwszemu w przypadku transformacji unary [...].

co oznacza, że ​​operacje na miejscu są wyraźnie dozwolone.

Teraz

[algorytms.parallel.overloads.2]

O ile nie określono inaczej, semantyka przeciążeń algorytmu ExecutionPolicy jest identyczna z ich przeciążeniami bez.

oznacza, że ​​polityka wykonywania nie ma widocznej różnicy w algorytmie. Można oczekiwać, że algorytm przyniesie dokładnie taki sam wynik, jakby nie określono zasady wykonywania.

Timo
źródło
6

Uważam, że chodzi o inny szczegół. unary_opBierze element sekwencji i zwraca wartość. Ta wartość jest zapisywana (przez transform) w sekwencji docelowej.

Więc unary_opbyłoby dobrze:

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

ale ten nie:

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

Ale tak naprawdę nie o to pytasz. Chcesz wiedzieć, czy możesz użyć unary_opwersji transformjako algorytmu równoległego z tym samym zakresem źródłowym i docelowym. Nie rozumiem dlaczego nie. transformodwzorowuje pojedynczy element sekwencji źródłowej na pojedynczy element sekwencji docelowej. Jeśli jednak unary_opnie jest tak naprawdę jednoznaczny (tzn. Odwołuje się do innych elementów w sekwencji - nawet jeśli tylko je odczytuje, oznacza to wyścig danych).

Marshall Clow
źródło
1

Jak widać na przykładzie cytowanego linku, modyfikowanie dowolnych elementów nie oznacza wszystkich rodzajów modyfikacji elementów:

Podpis funkcji powinien być równoważny z następującym:

Ret fun(const Type &a);

Obejmuje to modyfikację elementów. W najgorszym przypadku, jeśli użyjesz tego samego iteratora jako miejsca docelowego, modyfikacja nie powinna spowodować unieważnienia iteratorów, np. push_backWektora lub erasing, z vectorktórego prawdopodobnie spowoduje unieważnienie iteratorów.

Zobacz przykład niepowodzenia, którego NIE POWINIENEŚ robić na żywo .

Zapomnienie
źródło