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?

Nikos Athanasiou
źródło
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)
TemplateRex
źródło
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ń.

101010
źródło
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