c++ - Enlaces estructurados con std:: minmax y rvalues
c++17 structured-bindings (2)
Aquí hay dos cuestiones fundamentales:
-
min
,max
yminmax
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. Siminmax
te dio unpair<int, int>
aquí en lugar de unpair<int const&, int const&>
, no tendrías ningún problema. -
auto
decae el nivel superior cv -califica y elimina las referencias, pero no elimina todo el camino hacia abajo. Aquí, está deduciendo esepair<int const&, int const&>
, pero si hubiéramos deducido elpair<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.