Jakie są zastosowania decltype (auto)?


151

W języku c ++ 14 wprowadzono decltype(auto)idiom.

Zwykle służy do zezwalania autodeklaracjom na używanie decltypereguł dla danego wyrażenia .

Szukając przykładów „dobrego” użycia idiomu, przychodzą mi do głowy tylko następujące rzeczy (autorstwa Scotta Meyersa ), a mianowicie dedukcja typu zwracanego funkcji :

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

Czy są jakieś inne przykłady, w których ta nowa funkcja języka jest przydatna?


2
ten post zasadniczo sugeruje, aby spróbować uniknąć tego idiomu, ponieważ używając go dajesz mniej opcji optymalizacji dla swojego kompilatora stackoverflow.com/a/20092875/2485710
— user2485710

Kiedyś użyłem decltype(auto)czegoś podobnego template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, ale potem zdałem sobie sprawę, że muszę użyć, return (p.first);co zadziwiająco działa (ale IIRC jest nawet przeznaczone).
— dyp

@ user2485710 nie jestem pewien, czy chodzi konkretnie o optymalizację, a bardziej o możliwość wypadków, jeśli decltype(auto)może to spowodować skopiowanie / przeniesienie czegoś do zadeklarowanego obiektu, wbrew oczekiwaniom.
— underscore_d

Odpowiedzi:


170

Przekazywanie typu zwrotnego w kodzie ogólnym

W przypadku kodu nieogólnego, takiego jak podany w początkowym przykładzie, możesz ręcznie wybrać, aby uzyskać odwołanie jako typ zwracany:

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

ale w kodzie ogólnym chcesz mieć możliwość perfekcyjnego przekazywania zwracanego typu bez wiedzy, czy masz do czynienia z odwołaniem, czy z wartością. decltype(auto)daje ci tę możliwość:

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

Opóźnianie potrącenia typu zwrotu w szablonach cyklicznych

W tym pytaniu i odpowiedzi kilka dni temu napotkano nieskończoną rekursję podczas tworzenia wystąpienia szablonu, gdy typ zwracanego szablonu został określony jako decltype(iter(Int<i-1>{}))zamiast 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)jest tutaj używany do opóźnienia odliczenia typu zwracanego po ustąpieniu kurzu instancji szablonu.

Inne zastosowania

Można również używać decltype(auto)w innych kontekstach, np. W projekcie normy N3936 podano również

7.1.6.4 specyfikacja automatyczna [dcl.spec.auto]

1 autoi decltype(auto)typu specy fi ers wyznaczyć zastępczy typ, który zostanie zastąpiony później, albo poprzez odliczenie od inicjatora lub wyraźnej specy fi kacji z zwrotny typu wleczonego. Specyfikator autotypu jest również używany do wskazania, że ​​lambda to rodzajowa lambda.

2 Typ symbolu zastępczego może pojawić się z deklaratorem funkcji w sekwencji-specyfikatora-deklaracji, sekwencji-specyfikatora-typu, id-funkcji-konwersji lub typ-zwracanego-końcowego- w dowolnym kontekście, w którym taki deklarator jest prawidłowy . Jeśli deklarator funkcji zawiera końcowy-zwracany-typ (8.3.5), określa on zadeklarowany typ zwracania funkcji. Jeśli zadeklarowany zwracany typ funkcji zawiera typ zastępczy, zwracany typ funkcji jest wywnioskowany z instrukcji return w treści funkcji, jeśli takie istnieją.

Wersja robocza zawiera również następujący przykład inicjalizacji zmiennej:

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
Czy tarasowy zachowanie (i)vs inową rzeczą w C ++ 14?
— Danvil,

14
@Danvil decltype(expr)i decltype((expr))są już inne w C ++ 11, to uogólnia to zachowanie.
— TemplateRex,

13
Właśnie się tego nauczyłem, wydaje mi się, że to straszna decyzja projektowa ... dodając punktualny niuans do znaczenia składni nawiasów.
— Kahler

Przykładem, który zawsze wywołuje to obrzydzenie, jest jednowierszowa składnia pliku do ciągu (również wspomniana w tym łączu). Każda jego część wydaje się zacofana. Możesz w ogóle nie oczekiwać dwuznaczności i kompulsywnie usunąć zbędne nawiasy z próbki; można by oczekiwać, że niejednoznaczność zostanie rozwiązana w procesie eliminacji zgodnie z SFINAE, ale potencjalni kandydaci inni niż deklaracja są eliminowani z góry (SF to AE); i sfrustrowany możesz przejść dalej, gdy tylko skompiluje się myślenie, że arbitralne pareny rozwiązują niejednoznaczność, ale wprowadzają ją. Wyobrażam sobie, że najbardziej irytujące dla profesorów CS101.
— John P

@TemplateRex: O opóźnieniu rozpoznawania typu zwracanego w przywoływanym pytaniu: O ile widzę, w konkretnym scenariuszu prosty autowykonałby zadanie równie dobrze, ponieważ wynik i tak jest zwracany przez wartość ... A może przegapiłem coś?
— Aconcagua,

36

Cytując rzeczy z tego miejsca :

  • decltype(auto)jest przede wszystkim przydatny do określania zwracanego typu funkcji przekazujących i podobnych opakowań , w przypadku których chcesz, aby typ dokładnie „śledził” wywoływane wyrażenie.

  • Na przykład, biorąc pod uwagę poniższe funkcje:


   string  lookup1();
   string& lookup2();

  • W C ++ 11 mogliśmy napisać następujące funkcje opakowujące, które pamiętają o zachowaniu referencji zwracanego typu:

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

  • W C ++ 14 możemy to zautomatyzować:

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

  • Jednak decltype(auto)nie ma być szeroko używaną funkcją poza tym.

  • W szczególności, chociaż może być używany do deklarowania zmiennych lokalnych , zrobienie tego prawdopodobnie jest tylko anty-wzorcem, ponieważ odwołanie do zmiennej lokalnej nie powinno zależeć od wyrażenia inicjującego.

  • Ponadto jest wrażliwy na sposób pisania instrukcji return.

  • Na przykład dwie poniższe funkcje mają różne typy zwracanych wartości:


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

  • Pierwszy zwraca string, drugi zwraca string&, czyli odwołanie do zmiennej lokalnej str.

Z propozycji można zobaczyć więcej zamierzonych zastosowań.


3
Dlaczego nie użyć po prostu autodo zwrotu?
— BЈовић

@ BЈовић może również działać z uogólnioną dedukcją typu zwrotu (tj. Zwrotem auto), ale OP poprosił o użycie decltype(auto).
— 101010

3
Pytanie jest jednak nadal aktualne. Jaki byłby typ zwrotu auto lookup_a_string() { ... } ? Czy zawsze jest to typ bez odniesienia? Czy w związku z tym auto lookup_a_string() ->decltype(auto) { ... }konieczne jest wymuszenie zezwolenia na (w niektórych przypadkach) zwrotu referencji?
— Aaron McDaid

@AaronMcDaid Deductible autojest zdefiniowane w ramach szablonu pass by value, więc tak, nie może być odniesieniem. Proszę czekać, automoże to być oczywiście wszystko, łącznie z odniesieniem.
— curiousguy

4
Dodatkowym przykładem, o którym warto wspomnieć, jest zwrócenie elementu a std::vector. Powiedz, że masz template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Następnie S<bool>::operator[]zwróci wiszące referencje ze względu na specjalizację std::vector<bool>. Zmiana typu powrotu w celu decltype(auto)obejścia tego problemu.
— Xoph
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.