c++ - ¿Hay casos de uso realistas para las variables `decltype(auto)`?
c++14 type-deduction (2)
Tanto por mi experiencia personal como por la consulta de respuestas a preguntas como
¿Cuáles son algunos de los usos de decltype (auto)?
Puedo encontrar muchos casos de uso valiosos para
decltype(auto)
como
marcador de posición de tipo de retorno de función
.
Sin embargo, me
decltype(auto)
en un caso de uso válido (es decir, útil, realista, valioso) para
decltype(auto)
variables
decltype(auto)
.
La única posibilidad que viene a la mente es almacenar el resultado de una función que devuelve
decltype(auto)
para su posterior propagación, pero
auto&&
podría usarse allí y sería más simple.
Incluso he buscado en todos mis proyectos y experimentos, y las 391 apariciones de
decltype(auto)
son todos marcadores de posición de tipo de retorno.
Entonces,
¿hay casos de uso realistas para
decltype(auto)
variables
decltype(auto)
?
¿O esta característica solo es útil cuando se usa como marcador de posición de tipo de retorno?
¿Cómo se define "realista"?
Estoy buscando un caso de uso que proporcione valor
(es decir, no es solo un ejemplo para mostrar cómo funciona la función)
donde
decltype(auto)
es la opción perfecta, en comparación con alternativas como
auto&&
o no declarar una variable en absoluto.
El dominio del problema no importa, podría ser un oscuro caso de metaprogramación o una construcción de programación funcional arcana. Sin embargo, el ejemplo necesitaría hacerme decir "¡Hey, eso es inteligente / hermoso!" y usar cualquier otra característica para lograr el mismo efecto requeriría más repetitivo o tendría algún tipo de inconveniente.
Esencialmente, el caso de las variables es el mismo para las funciones.
La idea es que almacenemos el resultado de una invocación de función con una
decltype(auto)
:
decltype(auto) result = /* function invocation */;
Entonces, el
result
es
-
un tipo sin referencia si el resultado es un prvalue,
-
un tipo de referencia de lvalue (posiblemente calificado por cv) si el resultado es un lvalue, o
-
un tipo de referencia rvalue si el resultado es un xvalue.
Ahora necesitamos una nueva versión de
forward
para diferenciar entre el caso prvalue y el caso xvalue: (se evita el nombre
forward
para evitar problemas de ADL)
template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
return std::forward<T>(arg);
}
Y luego usar
my_forward<decltype(result)>(result)
A diferencia de
std::forward
, esta función se utiliza para reenviar
decltype(auto)
.
Por lo tanto, no devuelve incondicionalmente un tipo de referencia, y se supone que debe llamarse con
decltype(variable)
, que puede ser
T
,
T&
, o
T&&
, para que pueda diferenciar entre valores, valores x y valores.
Por lo tanto, si el
result
es
-
un tipo sin referencia, luego se llama a la segunda sobrecarga con una
T
sin referencia y se devuelve un tipo sin referencia, lo que da como resultado un valor prvaluente; -
un tipo de referencia lvalue, luego la primera sobrecarga se llama con un
T&
, yT&
se devuelve, lo que resulta en un lvalue; -
un tipo de referencia rvalue, luego se llama a la segunda sobrecarga con un
T&&
, y se devuelveT&&
, lo que resulta en un xvalue.
Aquí hay un ejemplo.
Tenga en cuenta que desea ajustar
std::invoke
e imprimir algo en el registro: (el ejemplo es solo ilustrativo)
template <typename F, typename... Args>
decltype(auto) my_invoke(F&& f, Args&&... args)
{
decltype(auto) result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
my_log("invoke", result); // for illustration only
return my_forward<decltype(result)>(result);
}
Ahora, si la expresión de invocación es
-
un prvalue, entonces el
result
es un tipo sin referencia, y la función devuelve un tipo sin referencia; -
un valor de valor no constante, entonces el
result
es una referencia de valor no constante y la función devuelve un tipo de referencia de valor no constante; -
un valor constante, entonces el
result
es una referencia constante y la función devuelve un tipo de referencia constante; -
un valor x, entonces el
result
es un tipo de referencia rvalue, y la función devuelve un tipo de referencia rvalue.
Dadas las siguientes funciones:
int f();
int& g();
const int& h();
int&& i();
se sostienen las siguientes afirmaciones:
static_assert(std::is_same_v<decltype(my_invoke(f)), int>);
static_assert(std::is_same_v<decltype(my_invoke(g)), int&>);
static_assert(std::is_same_v<decltype(my_invoke(h)), const int&>);
static_assert(std::is_same_v<decltype(my_invoke(i)), int&&>);
( demostración en vivo , mover solo caso de prueba )
Si se usa
auto&&
su lugar, el código tendrá algunos problemas para diferenciar entre prvalues y xvalues.
Probablemente no sea una respuesta muy profunda, pero básicamente
se propuso
decltype(auto)
para la deducción del tipo de retorno, para poder deducir referencias cuando el tipo de retorno es en realidad una referencia (al contrario del
auto
simple que nunca deducirá la referencia, o
auto&&
que siempre lo hará).
El hecho de que también se pueda utilizar para la declaración de variables no significa necesariamente que debería haber escenarios
mejores que otros
.
De hecho, el uso de
decltype(auto)
en la declaración de variables solo complicará la lectura del código, dado que, para una declaración de variables, tiene exactamente el mismo significado.
Por otro lado, el formulario
auto&&
permite declarar una variable constante, mientras que
decltype(auto)
no.