Quali sono alcuni usi di decltype (auto)?


151

In c ++ 14 decltype(auto)viene introdotto il linguaggio.

In genere il suo uso è consentireautodecltype alle dichiarazioni di usare le regole sull'espressione data .

Cercando esempi di "buon" uso del linguaggio posso solo pensare a cose come le seguenti (di Scott Meyers ), vale a dire la deduzione del tipo di ritorno di una funzione :

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

Ci sono altri esempi in cui questa nuova funzionalità linguistica è utile?


2
questo post fondamentalmente suggerisce di cercare di evitare questo idioma, perché quando lo si usa si stanno dando meno opzioni per l'ottimizzazione per il vostro compilatore stackoverflow.com/a/20092875/2485710
user2485710

Una volta ho usato decltype(auto)qualcosa di simile template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, anche se poi mi sono reso conto che dovevo usare ciò return (p.first);che sorprendentemente funziona (ma IIRC è persino destinato).
dyp

@utente2485710 non è sicuro che si tratti dell'ottimizzazione specifica, maggiore è il rischio di incidenti se un oggetto decltype(auto)può essere copiato / spostato nell'oggetto dichiarato, contrariamente alle aspettative.
underscore_d

Risposte:


170

Restituisce l'inoltro del tipo in codice generico

Per il codice non generico, come nell'esempio iniziale che hai fornito, puoi selezionare manualmente per ottenere un riferimento come tipo di ritorno:

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

ma nel codice generico vuoi essere in grado di inoltrare perfettamente un tipo restituito senza sapere se hai a che fare con un riferimento o un valore. decltype(auto)ti dà quell'abilità:

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

Ritardare la detrazione del tipo restituito nei modelli ricorsivi

In queste domande e risposte, alcuni giorni fa, si è verificata una ricorsione infinita durante l'istanza del modello quando il tipo di ritorno del modello è stato specificato come decltype(iter(Int<i-1>{}))anziché 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)viene utilizzato qui per ritardare la detrazione del tipo restituito dopo che si è risolta la polvere dell'istanza del modello.

Altri usi

È inoltre possibile utilizzare decltype(auto)in altri contesti, ad esempio la bozza della norma N3936 afferma anche

7.1.6.4 speci fi catore automatico [dcl.spec.auto]

1 I parametri autoe decltype(auto)type designano un tipo di segnaposto che verrà sostituito in seguito, deducendo da un inizializzatore o specificando esplicitamente un tipo-trailing-return. Lo autospeci fi catore di tipo viene utilizzato anche per indicare che un lambda è un lambda generico.

2 Il tipo di segnaposto può apparire con un dichiaratore di funzione in decl-specificatore-seq, tipo-specificatore-seq, conversione-funzione-id o tipo-trailing-return, in qualsiasi contesto in cui tale dichiaratore è valido . Se il dichiaratore di funzione include un tipo di ritorno finale (8.3.5), che specifica il tipo di ritorno dichiarato della funzione. Se il tipo di ritorno dichiarato della funzione contiene un tipo di segnaposto, il tipo di ritorno della funzione viene dedotto dalle dichiarazioni di ritorno nel corpo della funzione, se presenti.

La bozza contiene anche questo esempio di inizializzazione variabile:

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)

17
Il comportamento diverso di (i)vs è iuna novità in C ++ 14?
Danvil,

14
@Danvil decltype(expr)e decltype((expr))sono già diversi in C ++ 11, questo generalizza quel comportamento.
TemplateRex

13
Ho appena imparato questo, sembra una terribile decisione di progettazione ... aggiungendo una sfumatura puntuale al significato della sintassi delle parentesi.
Kahler,

L'esempio che richiede sempre questo disgusto è la sintassi da file a stringa di un liner (menzionata anche in quel link). Ogni parte sembra arretrata. Potresti non aspettarti affatto ambiguità e rimuovere compulsivamente le parentesi ridondanti da un campione; ci si aspetterebbe che l'ambiguità si risolva attraverso il processo di eliminazione secondo la SFINAE, ma i candidati potenziali diversi dalla dichiarazione vengono eliminati in anticipo (SF è AE); e nella frustrazione potresti andare avanti non appena si compila pensando che le parentesi arbitrarie risolvano l'ambiguità, ma la introducono . Immagino più fastidioso per i professori CS101.
John P

@TemplateRex: Informazioni sul ritardo della risoluzione del tipo di ritorno nella domanda di riferimento: per quanto vedo, nello scenario specifico , un semplice autoavrebbe svolto il lavoro altrettanto bene, poiché il risultato è stato comunque restituito in base al valore ... O mi sono perso qualcosa?
Aconcagua,

36

Citando cose da qui :

  • decltype(auto)è utile principalmente per dedurre il tipo restituito di funzioni di inoltro e wrapper simili , in cui si desidera che il tipo "traccia" esattamente un'espressione che si sta invocando.

  • Ad esempio, date le funzioni seguenti:


   string  lookup1();
   string& lookup2();

  • In C ++ 11 potremmo scrivere le seguenti funzioni wrapper che ricordano di preservare il riferimento del tipo restituito:

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

  • In C ++ 14, possiamo automatizzare che:

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

  • Tuttavia, decltype(auto)non intende essere una funzionalità ampiamente utilizzata oltre a ciò.

  • In particolare, sebbene possa essere usato per dichiarare variabili locali , farlo probabilmente è solo un antipattern poiché il riferimento di una variabile locale non dovrebbe dipendere dall'espressione di inizializzazione.

  • Inoltre, è sensibile al modo in cui scrivi l'istruzione return.

  • Ad esempio, le due funzioni seguenti hanno tipi di restituzione diversi:


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

  • Il primo restituisce string, il secondo restituisce string&, che è un riferimento alla variabile locale str.

Dalla proposta puoi vedere più usi previsti.


3
Perché non usare solo autoper il reso?
BЈовић,

@ BЈовић potrebbe funzionare anche con detrazione del tipo di reso generalizzato (ovvero, autoreturn) ma l'OP ha chiesto specificamente l'uso di decltype(auto).
101010

3
La domanda è comunque rilevante. Di che tipo sarebbe il tipo di reso auto lookup_a_string() { ... } ? È sempre un tipo non di riferimento? E quindi auto lookup_a_string() ->decltype(auto) { ... }è necessario forzare per consentire la restituzione (in alcuni casi) dei riferimenti?
Aaron McDaid il

@AaronMcDaid La franchigia autoè definita in termini di modello pass by value, quindi sì, non può essere un riferimento. Attendere prego autopuò essere qualsiasi cosa incluso un riferimento, ovviamente.
curiousguy,

4
Un altro esempio degno di nota è la restituzione di un elemento di a std::vector. Di 'che hai template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Quindi S<bool>::operator[]restituirà un riferimento penzolante a causa della specializzazione di std::vector<bool>. La modifica del tipo restituito per decltype(auto)aggirare questo problema.
Xoph,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.