c++ c++14 auto type-deduction decltype-auto

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& , y T& 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 devuelve T&& , 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.