Quelles sont les utilisations de decltype (auto)?

151

Dans C ++ 14, l' decltype(auto)idiome est introduit.

En règle générale, son utilisation est d' autoriser les autodéclarations à utiliser les decltyperègles sur l'expression donnée .

En recherchant des exemples de "bonne" utilisation de l'idiome, je ne peux penser qu'à des choses comme celles-ci (par Scott Meyers ), à savoir la déduction du type de retour d'une fonction :

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

Y a-t-il d'autres exemples où cette nouvelle fonctionnalité de langage est utile?

Nikos Athanasiou
la source
2
cet article suggère essentiellement d'essayer d'éviter cet idiome car lorsque vous l'utilisez, vous donnez moins d'options d'optimisation à votre compilateur stackoverflow.com/a/20092875/2485710
user2485710
Une fois, j'ai utilisé decltype(auto)pour quelque chose qui ressemble à template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, bien que je me sois alors rendu compte que je devais utiliser return (p.first);ce qui fonctionne étonnamment (mais IIRC c'est même prévu).
dyp
@ user2485710 n'est pas sûr qu'il s'agisse d'optimisation en particulier, plus le potentiel d'accidents decltype(auto)peut entraîner la copie / le déplacement de quelque chose dans l'objet déclaré, contrairement aux attentes.
underscore_d

Réponses:

170

Transfert de type de retour en code générique

Pour le code non générique, comme l'exemple initial que vous avez donné, vous pouvez sélectionner manuellement pour obtenir une référence comme type de retour:

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

mais en code générique, vous voulez pouvoir transmettre parfaitement un type de retour sans savoir si vous avez affaire à une référence ou à une valeur. decltype(auto)vous donne cette capacité:

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

Retarder la déduction du type de retour dans les modèles récursifs

Dans ce Q&A il y a quelques jours, une récursion infinie lors de l'instanciation du modèle a été rencontrée lorsque le type de retour du modèle a été spécifié comme decltype(iter(Int<i-1>{}))au lieu 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)est utilisé ici pour retarder la déduction du type de retour une fois que la poussière de l'instanciation du modèle s'est déposée.

Autres utilisations

Vous pouvez également utiliser decltype(auto)dans d'autres contextes, par exemple le projet de norme N3936 stipule également

7.1.6.4 spéci fi cateur automatique [dcl.spec.auto]

1 Les spéci fi cateurs de type autoet decltype(auto)désignent un type d'espace réservé qui sera remplacé ultérieurement, soit par déduction d'un initialiseur, soit par spéci fi cation explicite avec un type de retour de fin. Le autospécificateur de type est également utilisé pour signifier qu'un lambda est un lambda générique.

2 Le type d'espace réservé peut apparaître avec un déclarateur de fonction dans le fichier decl-specifier-seq, type-specifier-seq, conversion-function-id ou trailing-return-type, dans tout contexte où un tel déclarateur est valide . Si le déclarateur de fonction inclut un type de retour de fin (8.3.5), qui spécifie le type de retour déclaré de la fonction. Si le type de retour déclaré de la fonction contient un type d'espace réservé, le type de retour de la fonction est déduit des instructions de retour dans le corps de la fonction, le cas échéant.

Le brouillon contient également cet exemple d'initialisation de 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
la source
17
Le comportement différent de (i)vs est i-il nouveau dans C ++ 14?
Danvil
14
@Danvil decltype(expr)et decltype((expr))sont déjà différents en C ++ 11, cela généralise ce comportement.
TemplateRex
13
Je viens d'apprendre cela, cela ressemble à une terrible décision de conception ... ajouter une nuance ponctuelle à la signification syntaxique des parenthèses.
Kahler
L'exemple qui suscite toujours ce dégoût est la syntaxe fichier à chaîne à une ligne (également mentionnée dans ce lien). Chaque partie semble en arrière. Vous ne vous attendez peut-être pas du tout à une ambiguïté et supprimez les parenthèses redondantes d'un échantillon de manière compulsive; vous vous attendez à ce que l'ambiguïté soit résolue par un processus d'élimination selon SFINAE, mais les candidats potentiels autres que la déclaration sont éliminés à l'avance (SF est AE); et par frustration, vous pourriez passer à autre chose dès qu'il se compile en pensant que les parens arbitraires résolvent l'ambiguïté, mais ils l' introduisent . Le plus vexant pour les professeurs CS101 j'imagine.
John P
@TemplateRex: À propos du retard de la résolution du type de retour dans la question référencée: Pour autant que je vois, dans le scénario spécifique , un simple autoaurait tout aussi bien fait le travail, car le résultat est retourné par valeur de toute façon ... Ou ai-je manqué quelque chose?
Aconcagua
36

Citant des trucs d' ici :

  • decltype(auto)est principalement utile pour déduire le type de retour des fonctions de transfert et des wrappers similaires , où vous voulez que le type «suive» exactement une expression que vous invoquez.

  • Par exemple, étant donné les fonctions ci-dessous:


   string  lookup1();
   string& lookup2();

  • En C ++ 11, nous pourrions écrire les fonctions wrapper suivantes qui se souviennent de préserver la référence du type de retour:

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

  • En C ++ 14, nous pouvons automatiser cela:

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

  • Cependant, decltype(auto)n'est pas destiné à être une fonctionnalité largement utilisée au-delà de cela.

  • En particulier, bien que cela puisse être utilisé pour déclarer des variables locales , cela n'est probablement qu'un anti-modèle car la référence d'une variable locale ne doit pas dépendre de l'expression d'initialisation.

  • En outre, il est sensible à la façon dont vous écrivez l'instruction return.

  • Par exemple, les deux fonctions ci-dessous ont des types de retour différents:


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

  • Le premier retourne string, le second retourne string&, qui est une référence à la variable locale str.

À partir de la proposition, vous pouvez voir d'autres utilisations prévues.

101010
la source
3
Pourquoi ne pas simplement l'utiliser autopour le retour?
BЈовић
@ BЈовић pourrait également fonctionner avec une déduction de type retour généralisée (c'est-à-dire autoretour), mais l'OP a demandé spécifiquement des utilisations de decltype(auto).
101010
3
La question est cependant toujours d'actualité. Quel serait le type de retour auto lookup_a_string() { ... } ? Est-ce toujours un type sans référence? Et donc auto lookup_a_string() ->decltype(auto) { ... }est-il nécessaire de forcer pour permettre aux références d'être (dans certains cas) retournées?
Aaron McDaid
@AaronMcDaid Deductible autoest défini en terme de modèle de passage par valeur, donc oui, il ne peut pas être une référence. Please-wait autopeut être n'importe quoi, y compris une référence, bien sûr.
curiousguy
4
Un autre exemple qui mérite d'être mentionné est le renvoi d'un élément d'un std::vector. Dites que vous avez template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Puis S<bool>::operator[]retournera des références pendantes en raison de la spécialisation de std::vector<bool>. Changer le type de retour pour decltype(auto)contourner ce problème.
Xoph