¿Puedo usar std :: transform en su lugar con una política de ejecución paralela?

11

Si no me equivoco, puedo hacer que std::transformfuncione en su lugar usando el mismo rango que un iterador de entrada y salida. Supongamos que tengo algún std::vectorobjeto vec, entonces escribiría

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

utilizando una operación unaria adecuada unary_op.

Usando el estándar C ++ 17, me gustaría ejecutar la transformación en paralelo pegando una std::execution::parcomo el primer argumento. Esto haría que la función pasara de sobrecarga (1) a (2) en el artículo cppreference sobrestd::transform . Sin embargo, los comentarios a esta sobrecarga dicen:

unary_op[...] no debe invalidar ningún iterador, incluidos los iteradores finales, ni modificar ningún elemento de los rangos involucrados. (desde C ++ 11)

¿"Modificar algún elemento" realmente significa que no puedo usar el algoritmo en el lugar o se trata de un detalle diferente que interpreté mal?

geo
fuente

Respuestas:

4

Para citar el estándar aquí

[alg.transform.1]

op no [...] invalidará iteradores o subranges, ni modificará elementos en los rangos

esto le prohíbe unary_opmodificar el valor dado como argumento o el contenedor en sí.

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

Sin embargo, lo siguiente está bien.

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
}

Independiente de lo UnaryOperationque tenemos

[alg.transform.5]

el resultado puede ser igual al primero en caso de transformación unaria [...].

lo que significa que las operaciones en el lugar están explícitamente permitidas.

Ahora

[algorms.parallel.overloads.2]

A menos que se especifique lo contrario, la semántica de las sobrecargas del algoritmo ExecutionPolicy son idénticas a sus sobrecargas sin.

significa que la política de ejecución no tiene una diferencia visible del usuario en el algoritmo. Puede esperar que el algoritmo produzca exactamente el mismo resultado que si no especificara una política de ejecución.

Timo
fuente
6

Creo que se trata de un detalle diferente. La unary_optoma un elemento de la secuencia y devuelve un valor. Ese valor se almacena (por transform) en la secuencia de destino.

Entonces esto unary_opestaría bien:

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

pero este no:

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

Pero eso no es realmente lo que estás preguntando. Desea saber si puede usar la unary_opversión de transformcomo un algoritmo paralelo con el mismo rango de origen y destino. No veo por qué no. transformasigna un solo elemento de la secuencia de origen a un único elemento de la secuencia de destino. Sin embargo, si tu unary_opno es realmente unario (es decir, hace referencia a otros elementos en la secuencia, incluso si solo los lee, entonces tendrás una carrera de datos).

Marshall Clow
fuente
1

Como puede ver en el ejemplo del enlace que citó, modificar cualquier elemento no significa todos los tipos de modificación en los elementos:

La firma de la función debe ser equivalente a la siguiente:

Ret fun(const Type &a);

Eso incluye modificaciones en los elementos. En el peor de los casos, si utiliza el mismo iterador para el destino, la modificación no debe causar la invalidación de los iteradores, por ejemplo, a un push_backvector o erasing desde el vectorcual probablemente causará la invalidación de los iteradores.

Vea un ejemplo de falla que NO DEBE hacer en vivo .

Olvido
fuente