Quais são alguns usos do decltype (auto)?

151

No c ++ 14, o decltype(auto)idioma é introduzido.

Normalmente, seu uso é permitir que as autodeclarações usem as decltyperegras na expressão especificada .

Procurando exemplos de uso "bom" do idioma, só consigo pensar em coisas como as seguintes (de Scott Meyers ), especificamente para dedução do tipo de retorno de uma função :

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

Existem outros exemplos em que esse novo recurso de idioma é útil?

Nikos Athanasiou
fonte
2
este post basicamente sugerem para tentar evitar essa expressão porque quando usá-lo você está dando menos opções para a otimização de seu compilador stackoverflow.com/a/20092875/2485710
user2485710
Uma vez eu costumava usar decltype(auto)algo parecido template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, mas percebi que tinha que usar o return (p.first);que surpreendentemente funciona (mas o IIRC é o que se destina).
dyp
@ user2485710 não tem certeza se trata de otimização especificamente, mais o potencial de acidentes se decltype(auto)pode fazer com que algo seja copiado / movido para o objeto declarado, contrariando as expectativas.
underscore_d

Respostas:

170

Encaminhamento de tipo de retorno em código genérico

Para código não genérico, como o exemplo inicial que você deu, você pode selecionar manualmente para obter uma referência como um tipo de retorno:

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

mas no código genérico você deseja encaminhar perfeitamente um tipo de retorno sem saber se está lidando com uma referência ou um valor. decltype(auto)lhe dá essa capacidade:

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

Atraso na dedução do tipo de retorno em modelos recursivos

Nestas perguntas e respostas , há alguns dias, uma recursão infinita durante a instanciação do modelo foi encontrada quando o tipo de retorno do modelo foi especificado como em decltype(iter(Int<i-1>{}))vez 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)é usado aqui para atrasar a dedução do tipo de retorno após a poeira da instanciação do modelo.

Outros usos

Você também pode usar decltype(auto)em outros contextos, por exemplo, o rascunho da norma N3936 também declara

7.1.6.4 especificador automático [dcl.spec.auto]

1 Os especificadores de tipo autoe decltype(auto)designam um tipo de espaço reservado que será substituído posteriormente, por dedução de um inicializador ou por especificação explícita com um tipo de retorno à direita. O autoespecificador de tipo também é usado para significar que um lambda é um lambda genérico.

2 O tipo de espaço reservado pode aparecer com um declarador de função no decl-specifier-seq, type-specifier-seq, conversion-id da função ou tipo de retorno à direita, em qualquer contexto em que esse declarador seja válido . Se o declarador da função incluir um tipo de retorno à direita (8.3.5), isso especifica o tipo de retorno declarado da função. Se o tipo de retorno declarado da função contiver um tipo de espaço reservado, o tipo de retorno da função será deduzido das instruções de retorno no corpo da função, se houver.

O rascunho também contém este exemplo de inicialização de variável:

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
fonte
17
O comportamento diferenciado de (i)vs ié algo novo no C ++ 14?
Danvil
14
@ Danvil decltype(expr)e já decltype((expr))são diferentes no C ++ 11, isso generaliza esse comportamento.
TemplateRex
13
Acabei de aprender isso, parece uma péssima decisão de design ... adicionando uma nuance pontual ao significado da sintaxe entre parênteses.
Kahler
O exemplo que sempre causa esse nojo é a sintaxe de arquivo a cadeia de uma linha (também mencionada nesse link). Cada parte parece atrasada. Você pode não esperar ambiguidade e remover parênteses redundantes de uma amostra compulsivamente; você esperaria que a ambiguidade fosse resolvida pelo processo de eliminação, de acordo com o SFINAE, mas candidatos a candidatos que não sejam a declaração são eliminados antecipadamente (SF é AE); e, frustrado, você pode seguir em frente assim que compilar pensando que os parênteses arbitrários resolvem a ambiguidade, mas eles a introduzem . Imagino que seja mais irritante para os professores do CS101.
John P
@TemplateRex: Sobre o atraso da resolução do tipo de retorno na pergunta referenciada: Até onde eu vejo, no cenário específico , um simples autoteria feito o trabalho tão bem quanto o resultado é retornado por valor de qualquer maneira ... Ou eu perdi alguma coisa?
Aconcagua
36

Citando coisas daqui :

  • decltype(auto)é útil principalmente para deduzir o tipo de retorno de funções de encaminhamento e wrappers similares , onde você deseja que o tipo “rastreie” exatamente alguma expressão que você está chamando.

  • Por exemplo, dadas as funções abaixo:


   string  lookup1();
   string& lookup2();

  • No C ++ 11, poderíamos escrever as seguintes funções de wrapper que lembram de preservar a referência do tipo de retorno:

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

  • No C ++ 14, podemos automatizar isso:

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

  • No entanto, decltype(auto)não se destina a ser um recurso amplamente usado além disso.

  • Em particular, embora possa ser usado para declarar variáveis ​​locais , isso provavelmente é apenas um antipadrão, já que a referência de uma variável local não deve depender da expressão de inicialização.

  • Além disso, é sensível à forma como você escreve a declaração de retorno.

  • Por exemplo, as duas funções abaixo têm 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); }

  • O primeiro retorna string, o segundo retorna string&, que é uma referência à variável local str.

A partir da proposta, você pode ver mais usos pretendidos.

101010
fonte
3
Por que não usar apenas autopara devolução?
BЈовић
O @ BЈовић também poderia trabalhar com dedução do tipo de retorno generalizado (ou seja, autoretorno), mas o OP solicitou especificamente o uso de decltype(auto).
101010
3
A questão ainda é relevante. Qual seria o tipo de retorno auto lookup_a_string() { ... } ? É sempre um tipo sem referência? E, portanto, auto lookup_a_string() ->decltype(auto) { ... }é necessário forçar para permitir que as referências sejam (em alguns casos) retornadas?
Aaron McDaid
A franquia @AaronMcDaid autoé definida em termos de modelo de passagem por valor, portanto, sim, não pode ser uma referência. Aguarde por favor autopode ser qualquer coisa, incluindo uma referência, é claro.
Curiousguy
4
Um exemplo adicional que vale a pena mencionar é retornar um elemento de a std::vector. Diga que você tem template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Em seguida S<bool>::operator[], retornará uma referência pendente devido à especialização de std::vector<bool>. Alterar o tipo de retorno para decltype(auto)contornar esse problema.
Xof