210

In c++14 the decltype(auto) idiom is introduced.

Typically its use is to allow auto declarations to use the decltype rules on the given expression.

Searching for examples of "good" usage of the idiom I can only think of things like the following (by Scott Meyers), namely for a function's return type deduction:

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

Are there any other examples where this new language feature is useful?

Nawaz
  • 353,942
  • 115
  • 666
  • 851
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • 2
    this post basically suggest to try to avoid this idiom because when using it you are giving less options for optimization to your compiler http://stackoverflow.com/a/20092875/2485710 – user2485710 Jun 08 '14 at 19:27
  • I once used `decltype(auto)` for something akin to `template decltype(auto) first(std::pair& p) { return p.first; }`, though I then realized I had to use `return (p.first);` which surprisingly works (but IIRC this is even intended). – dyp Jun 08 '14 at 19:50
  • @user2485710 not sure it's about optimisation specifically, more the potential for accidents if `decltype(auto)` can cause something to be copied/moved into the declared object, counter to expectation. – underscore_d Apr 17 '16 at 17:45
  • In the example you give above, if `container` is actually an rvalue I think using decltype(auto) can lead to accidental references to dangles. However you can return by ContainerType's value type and copy ellision should give you the same thing as decltype(auto) but safe to take as a reference https://godbolt.org/z/GsYjxs – Steve Bronder Oct 06 '20 at 18:37
  • Yeah here's another example, where the internal value of the container is destroyed but we ask for a reference to it from the function https://godbolt.org/z/7jE5Me – Steve Bronder Oct 07 '20 at 18:26

2 Answers2

239

Return type forwarding in generic code

For non-generic code, like the initial example you gave, you can manually select to get a reference as a return type:

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

but in generic code you want to be able to perfectly forward a return type without knowing whether you are dealing with a reference or a value. decltype(auto) gives you that ability:

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

Delaying return type deduction in recursive templates

In this Q&A a few days ago, an infinite recursion during template instantiation was encountered when the return type of the template was specified as decltype(iter(Int<i-1>{})) instead of 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) is used here to delay the return type deduction after the dust of template instantiation has settled.

Other uses

You can also use decltype(auto) in other contexts, e.g. the draft Standard N3936 also states

7.1.6.4 auto specifier [dcl.spec.auto]

1 The auto and decltype(auto) type-specifiers designate a placeholder type that will be replaced later, either by deduction from an initializer or by explicit specification with a trailing-return-type. The auto type-specifier is also used to signify that a lambda is a generic lambda.

2 The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (8.3.5), that specifies the declared return type of the function. If the declared return type of the function contains a placeholder type, the return type of the function is deduced from return statements in the body of the function, if any.

The draft also contains this example of variable initialization:

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)
Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • 23
    Is the differen behaviour of `(i)` vs `i` a new thing in C++14? – Danvil Jun 09 '14 at 00:37
  • 21
    @Danvil `decltype(expr)` and `decltype((expr))` are different in C++11 already, this generalizes that behaviour. – TemplateRex Jun 09 '14 at 05:33
  • 28
    I've just learned this, feels like a terrible design decision... adding a punctual nuance to the syntax meaning of parentheses. – Kahler Jan 02 '17 at 19:15
  • 3
    The example that always prompts this disgust is the one-liner file-to-string syntax (also mentioned in that link). Every part of it seems backward. You might not expect ambiguity at all, and remove redundant parentheses from a sample compulsively; you would expect ambiguity to resolve by process of elimination according to SFINAE, but would-be candidates other than the declaration are eliminated in advance (SF**is**AE); and in frustration you might move on as soon as it compiles thinking the arbitrary parens solve ambiguity, but they *introduce* it. Most vexing for CS101 professors I imagine. – John P Jan 01 '18 at 03:47
  • @TemplateRex: About delaying return type resolution in the referenced question: As far as I see, in the *specific scenario*, a simple `auto` would have done the job just as well, as the result is returned by value anyway... Or did I miss something? – Aconcagua Jan 18 '18 at 17:30
  • why in decltype(auto) x4d = (i); decltype(x4d) is int& ? – Bruice Jul 14 '21 at 11:54
  • 3
    @Bruice: "why in decltype(auto) x4d = (i); decltype(x4d) is int& ?" Decltype has two personalities. There's a (1) and (2): https://en.cppreference.com/w/cpp/language/decltype It can be applied to a name or to other expressions. decltype(i) means "the type of the entity named `i`", and decltype((i)) blocks that interpretation, and means "the type of an expression that evaluates to a reference to `i`". It's super subtle IMO but that's life. Now it matters when OCD makes people use parens around "return" expressions as if "return" was a function. :) Also makes macros trickier. – Billy Donahue Jan 20 '22 at 20:05
48

Quoting stuff from here:

  • decltype(auto) is primarily useful for deducing the return type of forwarding functions and similar wrappers, where you want the type to exactly “track” some expression you’re invoking.

  • For example, given the functions below:


   string  lookup1();
   string& lookup2();

  • In C++11 we could write the following wrapper functions which remember to preserve the reference-ness of the return type:

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

  • In C++14, we can automate that:

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

  • However, decltype(auto) is not intended to be a widely used feature beyond that.

  • In particular, although it can be used to declare local variables, doing that is probably just an antipattern since a local variable’s reference-ness should not depend on the initialization expression.

  • Also, it is sensitive to how you write the return statement.

  • For example, the two functions below have different return types:


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

  • The first returns string, the second returns string&, which is a reference to the local variable str.

From the proposal you can see more intended uses.

101010
  • 41,839
  • 11
  • 94
  • 168
  • 12
    Why not just use `auto` for return? – BЈовић Jul 01 '14 at 14:08
  • @BЈовић could work with generalized return type deduction (i.e., `auto` return) as well but the OP asked specifically for uses of `decltype(auto)`. – 101010 Jul 01 '14 at 19:52
  • 5
    The question is still relevant though. What would the return type be of `auto lookup_a_string() { ... } `? Is it always a non-reference type? And therefore `auto lookup_a_string() ->decltype(auto) { ... }` is needed to force to allows references to be (in some cases) returned? – Aaron McDaid Aug 07 '15 at 09:57
  • 2
    @AaronMcDaid Deductible `auto` is defined in term of pass by value template, so yes it cannot be a reference. Please-wait `auto` can be anything including a reference, of course. – curiousguy Aug 16 '15 at 00:20
  • Why does the second overload return a `string&`? I have never understood how does the parenthesis affect the type of the expression. – ABu May 01 '17 at 00:23
  • @Peregring-lk it's a core language feature... [7.1.7.2/p4 Simple type specifiers dcl.type.simple](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/n4618.pdf). – 101010 May 01 '17 at 16:48
  • 5
    An additional example worth mentioning is returning an element of a `std::vector`. Say you have `template struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector v; }`. Then `S::operator[]` will return a dangling references because of the specialization of `std::vector`. Changing the return type to `decltype(auto)` circumvents this problem. – Xoph May 30 '18 at 14:11
  • "Also, it is sensitive to how you write the return statement." that part isnt very clear, as in the examples before, the types deduces where the same as in the last case. If the `str` vs `(str)` makes a difference isnt clear from this example – 463035818_is_not_an_ai Sep 17 '18 at 11:38
  • If you understand the difference between `decltype(str)` and `decltype((str))`, you should understand the last example. – Weijun Zhou Mar 19 '20 at 21:31