¿Cuáles son algunos de los usos de decltype (auto)?

151

En c ++ 14 decltype(auto)se introduce el idioma.

Por lo general, su uso es permitir que las autodeclaraciones usen las decltypereglas en la expresión dada .

Buscando ejemplos de uso "bueno" del idioma solo puedo pensar en cosas como las siguientes (por Scott Meyers ), a saber, para la deducción de tipo de retorno de una función :

template<typename ContainerType, typename IndexType>                // C++14
decltype(auto) grab(ContainerType&& container, IndexType&& index)
{
  authenticateUser();
  return std::forward<ContainerType>(container)[std::forward<IndexType>(index)];
}

¿Hay otros ejemplos en los que esta nueva función de lenguaje sea útil?

Nikos Athanasiou
fuente
2
Esta publicación básicamente sugiere tratar de evitar este modismo porque al usarlo le está dando menos opciones de optimización a su compilador stackoverflow.com/a/20092875/2485710
user2485710
Una vez solía usar decltype(auto)algo similar template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, aunque luego me di cuenta de que tenía que usarlo, lo return (p.first);que sorprendentemente funciona (pero IIRC, incluso está destinado).
dyp
@ user2485710 no estoy seguro de que se trate específicamente de la optimización, más el potencial de accidentes si decltype(auto)puede hacer que algo se copie / mueva al objeto declarado, en contra de lo esperado.
underscore_d

Respuestas:

170

Reenvío de tipo de reenvío en código genérico

Para el código no genérico, como el ejemplo inicial que proporcionó, puede seleccionar manualmente para obtener una referencia como tipo de retorno:

auto const& Example(int const& i) 
{ 
    return i; 
}

pero en el código genérico desea poder reenviar perfectamente un tipo de retorno sin saber si se trata de una referencia o un valor. decltype(auto)te da esa habilidad:

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

Retraso en la deducción del tipo de devolución en plantillas recursivas

En estas preguntas y respuestas de hace unos días, se encontró una recursión infinita durante la creación de instancias de plantilla cuando se especificó el tipo de retorno de la plantilla en decltype(iter(Int<i-1>{}))lugar de decltype(auto).

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) 
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

decltype(auto)se utiliza aquí para retrasar la deducción del tipo de devolución después de que se haya establecido el polvo de la creación de instancias de plantilla.

Otros usos

También puede usarlo decltype(auto)en otros contextos, por ejemplo, el borrador del Estándar N3936 también establece

7.1.6.4 autoespecificador [dcl.spec.auto]

1 El autoy decltype(auto)tipo específico ERS designar un tipo de marcador de posición que será reemplazado más tarde, ya sea por deducción a partir de un inicializador o por explícita catiónico específico con una de tipo de retorno de arrastre. El autoespecificador de tipo también se usa para indicar que una lambda es una lambda genérica.

2 El tipo de marcador de posición puede aparecer con un declarador de función en decl-specifier-seq, type-specifier-seq, conversion-function-id o trailing-return-type, en cualquier contexto en el que dicho declarador sea válido . Si el declarador de función incluye un tipo de retorno de seguimiento (8.3.5), eso especifica el tipo de retorno declarado de la función. Si el tipo de retorno declarado de la función contiene un tipo de marcador de posición, el tipo de retorno de la función se deduce de las declaraciones de retorno en el cuerpo de la función, si corresponde.

El borrador también contiene este ejemplo de inicialización variable:

int i;
int&& f();
auto x3a = i;                  // decltype(x3a) is int
decltype(auto) x3d = i;        // decltype(x3d) is int
auto x4a = (i);                // decltype(x4a) is int
decltype(auto) x4d = (i);      // decltype(x4d) is int&
auto x5a = f();                // decltype(x5a) is int
decltype(auto) x5d = f();      // decltype(x5d) is int&&
auto x6a = { 1, 2 };           // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i;                // decltype(x7a) is int*
decltype(auto)*x7d = &i;       // error, declared type is not plain decltype(auto)
TemplateRex
fuente
17
¿Es el comportamiento diferente de (i)vs ialgo nuevo en C ++ 14?
Danvil
14
@Danvil decltype(expr)y ya decltype((expr))son diferentes en C ++ 11, esto generaliza ese comportamiento.
TemplateRex
13
Acabo de aprender esto, se siente como una terrible decisión de diseño ... agregando un matiz puntual al significado de sintaxis de paréntesis.
Kahler
El ejemplo que siempre provoca este disgusto es la sintaxis de una línea de archivo a cadena (también mencionada en ese enlace). Cada parte parece al revés. Es posible que no espere ambigüedad en absoluto y elimine paréntesis redundantes de una muestra de forma compulsiva; esperaría que la ambigüedad se resolviera mediante el proceso de eliminación de acuerdo con SFINAE, pero los posibles candidatos que no sean la declaración se eliminan de antemano (SF es AE); y frustrado, puede seguir adelante tan pronto como se compila pensando que los padres arbitrarios resuelven la ambigüedad, pero la introducen . Lo más molesto para los profesores de CS101 que imagino.
John P
@TemplateRex: Acerca de retrasar la resolución del tipo de retorno en la pregunta mencionada: Por lo que veo, en el escenario específico , un simple autohabría hecho el trabajo igual de bien, ya que el resultado es devuelto por el valor de todos modos ... ¿O me perdí? ¿alguna cosa?
Aconcagua
36

Citando cosas de aquí :

  • decltype(auto)es principalmente útil para deducir el tipo de retorno de las funciones de reenvío y envoltorios similares , donde desea que el tipo "rastree" exactamente alguna expresión que está invocando.

  • Por ejemplo, dadas las siguientes funciones:


   string  lookup1();
   string& lookup2();

  • En C ++ 11 podríamos escribir las siguientes funciones de contenedor que recuerdan preservar la referencia del tipo de retorno:

   string  look_up_a_string_1() { return lookup1(); }
   string& look_up_a_string_2() { return lookup2(); }

  • En C ++ 14, podemos automatizar eso:

   decltype(auto) look_up_a_string_1() { return lookup1(); }
   decltype(auto) look_up_a_string_2() { return lookup2(); }

  • Sin embargo, decltype(auto)no pretende ser una característica ampliamente utilizada más allá de eso.

  • En particular, aunque puede usarse para declarar variables locales , hacerlo probablemente sea solo un antipatrón ya que la referencia de una variable local no debería depender de la expresión de inicialización.

  • Además, es sensible a cómo se escribe la declaración de devolución.

  • Por ejemplo, las dos funciones siguientes tienen diferentes tipos de retorno:


   decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
   decltype(auto) look_up_a_string_2() { auto str = lookup2(); return(str); }

  • El primero vuelve string, el segundo vuelve string&, que es una referencia a la variable local str.

De la propuesta puede ver más usos previstos.

101010
fuente
3
¿Por qué no solo usar autopara devolución?
B 1овић
@ BЈовић también podría funcionar con la deducción de tipo de devolución generalizada (es decir, autodevolución), pero el OP solicitó específicamente usos de decltype(auto).
101010
3
Sin embargo, la pregunta sigue siendo relevante. ¿Cuál sería el tipo de retorno auto lookup_a_string() { ... } ? ¿Es siempre un tipo sin referencia? ¿Y por auto lookup_a_string() ->decltype(auto) { ... }lo tanto se necesita forzar para permitir que se devuelvan referencias (en algunos casos)?
Aaron McDaid el
@AaronMcDaid El deducible autose define en términos de plantilla de pasar por valor, por lo que sí, no puede ser una referencia. Por favor, espere autopuede ser cualquier cosa, incluida una referencia, por supuesto.
curioso
44
Un ejemplo adicional que vale la pena mencionar es devolver un elemento de a std::vector. Di que tienes template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Luego S<bool>::operator[]devolverá unas referencias colgantes debido a la especialización de std::vector<bool>. Cambiar el tipo de retorno para decltype(auto)evitar este problema.
Xoph