Posso usare std :: transform sul posto con una politica di esecuzione parallela?


11

Se non sbaglio, posso far std::transformfunzionare sul posto usando lo stesso intervallo di un iteratore di input e output. Supponiamo di avere un std::vectoroggetto vec, quindi scrivo

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

usando un'operazione unaria adatta unary_op.

Usando lo standard C ++ 17, vorrei eseguire la trasformazione in parallelo inserendone una std::execution::parcome primo argomento. Ciò farebbe passare la funzione da overload (1) a (2) nell'articolo di cppreference sustd::transform . Tuttavia, i commenti a questo sovraccarico dicono:

unary_op[...] non deve invalidare alcun iteratore, inclusi gli iteratori finali, né modificare alcun elemento degli intervalli coinvolti. (dal C ++ 11)

"Modificare gli elementi" significa davvero che non posso usare l'algoritmo in atto o si tratta di un dettaglio diverso che ho frainteso?

Risposte:


4

Per citare lo standard qui

[Alg.transform.1]

op [...] non deve invalidare iteratori o subrange né modificare elementi negli intervalli

ciò vieta unary_opdi modificare il valore fornito come argomento o il contenitore stesso.

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

Tuttavia, il seguito è ok.

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
}

Indipendente dal UnaryOperationnostro

[Alg.transform.5]

il risultato può essere uguale al primo in caso di trasformazione unaria [...].

ciò significa che le operazioni sul posto sono esplicitamente consentite.

Adesso

[Algorithms.parallel.overloads.2]

Se non diversamente specificato, la semantica dei sovraccarichi dell'algoritmo ExecutionPolicy è identica ai loro sovraccarichi senza.

significa che la politica di esecuzione non ha alcuna differenza visibile dall'utente sull'algoritmo. Puoi aspettarti che l'algoritmo produca lo stesso esatto risultato se non specifichi una politica di esecuzione.


6

Credo che si tratti di un dettaglio diverso. Il unary_opprende un elemento della sequenza e restituisce un valore. Tale valore viene memorizzato (da transform) nella sequenza di destinazione.

Quindi questo unary_opandrebbe bene:

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

ma questo non dovrebbe:

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

Ma non è proprio quello che stai chiedendo. Volete sapere se è possibile utilizzare la unary_opversione di transformcome algoritmo parallelo con lo stesso intervallo di origine e destinazione. Non vedo perché no. transformassocia un singolo elemento della sequenza di origine a un singolo elemento della sequenza di destinazione. Tuttavia, se il tuo unary_opnon è realmente unario (cioè, fa riferimento ad altri elementi nella sequenza - anche se li legge solo, allora avrai una corsa di dati).


1

Come si può vedere nell'esempio del collegamento hai citato, modificando alcuni elementi non vuol dire tutti i tipi di modifica sugli elementi:

La firma della funzione dovrebbe essere equivalente alla seguente:

Ret fun(const Type &a);

Ciò include la modifica degli elementi. Nel peggiore dei casi se si utilizza lo stesso iteratore per destinazione, la modifica non dovrebbe causare l'invalidazione degli iteratori che è ad es. Un push_backvettore o erasing, da vectorcui probabilmente causerà l'invalidazione degli iteratori.

Vedi un esempio di errore che NON DOVREBBE fare dal vivo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.