Puis-je utiliser std :: transform en place avec une politique d'exécution parallèle?

11

Si je ne me trompe pas, je peux faire std::transformfonctionner en place en utilisant la même plage qu'un itérateur d'entrée et de sortie. Supposons que j'ai un std::vectorobjet vec, alors j'écrirais

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

en utilisant une opération unaire appropriée unary_op.

En utilisant la norme C ++ 17, je voudrais exécuter la transformation en parallèle en collant un std::execution::pardedans comme premier argument. Cela ferait passer la fonction de surcharge (1) à (2) dans l' article de référence surstd::transform . Cependant, les commentaires sur cette surcharge indiquent:

unary_op[...] ne doit invalider aucun itérateur, y compris les itérateurs finaux, ni modifier aucun élément des plages concernées. (depuis C ++ 11)

Est-ce que «modifier des éléments» signifie vraiment que je ne peux pas utiliser l'algorithme en place ou s'agit-il d'un détail différent que j'ai mal interprété?

géo
la source

Réponses:

4

Pour citer la norme ici

[alg.transform.1]

op [...] ne doit pas invalider les itérateurs ou les sous-plages, ni modifier les éléments des plages

cela vous interdit unary_opde modifier la valeur donnée en argument ou le conteneur lui-même.

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

Cependant, le follwing est 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
}

Indépendant du UnaryOperationnous avons

[alg.transform.5]

le résultat peut être égal au premier en cas de transformation unaire [...].

ce qui signifie que les opérations sur place sont explicitement autorisées.

Maintenant

[algorithms.parallel.overloads.2]

Sauf indication contraire, la sémantique des surcharges de l'algorithme ExecutionPolicy est identique à leurs surcharges sans.

signifie que la politique d'exécution n'a aucune différence visible par l'utilisateur sur l'algorithme. Vous pouvez vous attendre à ce que l'algorithme produise exactement le même résultat que si vous ne spécifiiez pas de politique d'exécution.

Timo
la source
6

Je pense qu'il s'agit d'un détail différent. Le unary_opprend un élément de la séquence et renvoie une valeur. Cette valeur est stockée (par transform) dans la séquence de destination.

Donc ce unary_opserait bien:

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

mais celui-ci ne serait pas:

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

Mais ce n'est pas vraiment ce que vous demandez. Vous voulez savoir si vous pouvez utiliser la unary_opversion de transformcomme un algorithme parallèle avec la même source et la même plage de destination. Je ne vois pas pourquoi pas. transformmappe un seul élément de la séquence source à un seul élément de la séquence de destination. Cependant, si vous unary_opn'êtes pas vraiment unaire (c'est-à-dire qu'il fait référence à d'autres éléments de la séquence - même s'il ne les lit que, vous aurez une course aux données).

Marshall Clow
la source
1

Comme vous pouvez le voir dans l'exemple du lien que vous avez cité, la modification des éléments ne signifie tous les types de modification sur les éléments suivants:

La signature de la fonction doit être équivalente à la suivante:

Ret fun(const Type &a);

Cela inclut la modification des éléments. Dans le pire des cas, si vous utilisez le même itérateur pour la destination, la modification ne doit pas provoquer l'invalidation des itérateurs, par exemple un push_backvecteur vers ou à eraspartir vectorduquel provoquera probablement l'invalidation des itérateurs.

Voir un exemple d'échec que vous NE DEVRIEZ PAS faire en direct .

Oubli
la source