Posso usar std :: transform no lugar com uma política de execução paralela?

11

Se não me engano, eu posso fazer std::transformexecutar no lugar usando a mesma faixa como um iterador de entrada e saída. Suponha que eu tenha algum std::vectorobjeto vec, então eu escreveria

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

usando uma operação unária adequada unary_op.

Usando o padrão C ++ 17, eu gostaria de executar a transformação em paralelo, colocando um std::execution::parlá como o primeiro argumento. Isso faria a função passar da sobrecarga (1) para (2) no artigo cppreference emstd::transform . No entanto, os comentários a essa sobrecarga dizem:

unary_op[...] não deve invalidar nenhum iterador, incluindo o iterador final, nem modificar nenhum elemento dos intervalos envolvidos. (desde C ++ 11)

"Modificar quaisquer elementos" realmente significa que não posso usar o algoritmo no lugar ou isso está falando de um detalhe diferente que eu interpretei errado?

geo
fonte

Respostas:

4

Para citar o padrão aqui

[alg.transform.1]

op [...] não deve invalidar iteradores ou subintervalos, nem modificar elementos nos intervalos

isso proíbe que você unary_opmodifique o valor fornecido como argumento ou o próprio contêiner.

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

No entanto, o seguimento 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
}

Independente do UnaryOperationque temos

[alg.transform.5]

resultado pode ser igual ao primeiro em caso de transformação unária [...].

ou seja, operações no local são explicitamente permitidas.

Agora

[algoritms.parallel.overloads.2]

A menos que especificado de outra forma, a semântica das sobrecargas do algoritmo ExecutionPolicy é idêntica às suas sobrecargas sem.

significa que a política de execução não possui diferença visível do usuário no algoritmo. Você pode esperar que o algoritmo produza exatamente o mesmo resultado, como se você não especificasse uma política de execução.

Timo
fonte
6

Eu acredito que está falando sobre um detalhe diferente. O unary_oppega um elemento da sequência e retorna um valor. Esse valor é armazenado (por transform) na sequência de destino.

Então, isso unary_opseria bom:

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

mas este não faria:

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

Mas não é exatamente isso que você está perguntando. Você quer saber se pode usar a unary_opversão transformcomo um algoritmo paralelo com a mesma faixa de origem e destino. Não vejo por que não. transformmapeia um único elemento da sequência de origem para um único elemento da sequência de destino. No entanto, se você unary_opnão é realmente unário (ou seja, ele faz referência a outros elementos na sequência - mesmo que apenas os leia, você terá uma corrida de dados).

Marshall Clow
fonte
1

Como você pode ver no exemplo do link que você citou, modificar qualquer elemento não significa todos os tipos de modificação nos elementos:

A assinatura da função deve ser equivalente ao seguinte:

Ret fun(const Type &a);

Isso inclui modificações nos elementos. Na pior das hipóteses, se você usar o mesmo iterador para o destino, a modificação não deve causar a invalidação de iteradores, por exemplo, um push_backvetor ou vetor erasdo vectorqual provavelmente causará a invalidação de iteradores.

Veja um exemplo de falha que você NÃO DEVE Live .

Esquecimento
fonte