c++ c++17 rvalue structured-bindings

c++ - Enlaces estructurados con std:: minmax y rvalues



c++17 structured-bindings (2)

Aquí hay dos cuestiones fundamentales:

  1. min , max y minmax por razones históricas devuelven referencias . Por lo tanto, si pasa de forma temporal, será mejor que tome el resultado por valor o lo use de inmediato, de lo contrario, obtendrá una referencia pendiente. Si minmax te dio un pair<int, int> aquí en lugar de un pair<int const&, int const&> , no tendrías ningún problema.
  2. auto decae el nivel superior cv -califica y elimina las referencias, pero no elimina todo el camino hacia abajo. Aquí, está deduciendo ese pair<int const&, int const&> , pero si hubiéramos deducido el pair<int, int> , nuevamente no tendríamos ningún problema.

(1) es un problema mucho más fácil de resolver que (2): escriba sus propias funciones para tomar todo por valor:

template <typename T> std::pair<T, T> minmax(T a, T b) { return (b < a) ? std::pair(b, a) : std::pair(a, b); } auto [amin, amax] = minmax(3, 6); // no problems

Lo bueno de tomar todo por valor es que nunca tiene que preocuparse por las referencias ocultas ocultas, porque no hay ninguna. Y la gran mayoría de los usos de estas funciones utilizan tipos integrales de todos modos, por lo que no hay beneficio para las referencias.

Y cuando necesita referencias, para cuando está comparando objetos costosos para copiar ... bueno, es más fácil tomar una función que toma valores y obligarla a usar referencias que a una función que usa referencias y intenta arreglarlo:

auto [lo, hi] = minmax(std::ref(big1), std::ref(big2));

Además, es muy visible aquí en el sitio de la llamada que estamos usando referencias, por lo que sería mucho más obvio si nos equivocamos.

Si bien lo anterior funciona para muchos tipos debido a la conversión implícita de reference_wrapper<T> a T& , no funcionará para aquellos tipos que tienen plantillas de operador que no son miembros ni amigos (como std::string ). Así que, además, necesitaría escribir una especialización para envoltorios de referencia, desafortunadamente.

Me encontré con un error bastante sutil al usar std::minmax con enlaces estructurados. Parece que los valores pasados ​​no siempre se copiarán como se podría esperar. Originalmente, estaba usando un T operator[]() const en un contenedor personalizado, pero parece ser lo mismo con un entero literal.

#include <algorithm> #include <cstdio> #include <tuple> int main() { auto [amin, amax] = std::minmax(3, 6); printf("%d,%d/n", amin, amax); // undefined,undefined int bmin, bmax; std::tie(bmin, bmax) = std::minmax(3, 6); printf("%d,%d/n", bmin, bmax); // 3,6 }

El uso de GCC 8.1.1 con -O1 -Wuninitialized dará como resultado la impresión de 0,0 como primera línea y:

warning: ‘<anonymous>’ is used uninitialized in this function [-Wuninitialized]

Clang 6.0.1 en -O2 también dará un primer resultado incorrecto sin advertencia.

En -O0 GCC da un resultado correcto y no hay advertencia . Para el clang el resultado parece ser correcto en -O1 o -O0 .

¿No deberían ser la primera y la segunda líneas equivalentes en el sentido de que el valor de r aún es válido para ser copiado?

Además, ¿por qué esto depende del nivel de optimización? En particular, me sorprendió que GCC no emita ninguna advertencia.


Lo que es importante tener en cuenta en auto [amin, amax] es que auto , auto , etc., se aplican al objeto creado e que se inicializa con el valor de retorno de std::minmax , que es un par. Es esencialmente esto:

auto e = std::minmax(3, 6); auto&& amin = std::get<0>(e); auto&& amax = std::get<1>(e);

Los tipos reales de amin y amax son referencias que se refieren a cualquier amax std::get<0> y std::get<1> para ese objeto de par. ¡Y ellos mismos devuelven referencias a objetos que se han ido!

Cuando usa std::tie , está haciendo una asignación a objetos existentes (pasados ​​por referencia). Los valores no necesitan vivir más tiempo que las expresiones de asignación en las que nacen.

Como solución alternativa, puede utilizar una función similar a esta (no a calidad de producción):

template<typename T1, typename T2> auto as_value(std::pair<T1, T2> in) { using U1 = std::decay_t<T1>; using U2 = std::decay_t<T2>; return std::pair<U1, U2>(in); }

Asegura que el par mantiene tipos de valor. Cuando se usa así:

auto [amin, amax] = as_value(std::minmax(3, 6));

Ahora obtenemos una copia, y los enlaces estructurados se refieren a esas copias.