What Are Some Uses of Decltype(Auto)

What are some uses of decltype(auto)?

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)

Are there any realistic use cases for `decltype(auto)` variables?

Probably not a very deep answer, but basically decltype(auto) was proposed to be used for return type deduction, to be able to deduce references when the return type is actually a reference (contrary to plain auto that will never deduce the reference, or auto&& that will always do it).

The fact that it can also be used for variable declaration not necessarily means that there should be better-than-other scenarios. Indeed, using decltype(auto) in variable declaration will just complicate the code reading, given that, for a variable declaration, is has exactly the same meaning. On the other hand, the auto&& form allows you to declare a constant variable, while decltype(auto) doesn't.

What is the difference between auto and decltype(auto) when returning from a function?

auto follows the template argument deduction rules and is always an object type; decltype(auto) follows the decltype rules for deducing reference types based on value categories. So if we have

int x;
int && f();

then

expression    auto       decltype(auto)
----------------------------------------
10 int int
x int int
(x) int int &
f() int int &&

decltype vs auto

decltype gives the declared type of the expression that is passed to it. auto does the same thing as template type deduction. So, for example, if you have a function that returns a reference, auto will still be a value (you need auto& to get a reference), but decltype will be exactly the type of the return value.

#include <iostream>
int global{};
int& foo()
{
return global;
}

int main()
{
decltype(foo()) a = foo(); //a is an `int&`
auto b = foo(); //b is an `int`
b = 2;

std::cout << "a: " << a << '\n'; //prints "a: 0"
std::cout << "b: " << b << '\n'; //prints "b: 2"

std::cout << "---\n";
decltype(foo()) c = foo(); //c is an `int&`
c = 10;

std::cout << "a: " << a << '\n'; //prints "a: 10"
std::cout << "b: " << b << '\n'; //prints "b: 2"
std::cout << "c: " << c << '\n'; //prints "c: 10"
}

Also see David Rodríguez's answer about the places in which only one of auto or decltype are possible.

When should I use decltype(x) instead of auto to declare the type of a variable?

You should use it when the required type of y is:

  • different (or potentially different) from the type of expr. If it was the same then auto would be more concise.
  • similarly for auto & or other modifications of the type of expr that auto can express.

and one of the following:

  • dependent on something in the surrounding code (i.e. not always the same type) and difficult to write using type traits or similar. This will tend to happen in template code. There might be a type trait that you can use to get the required type from the template parameters, but then again there might not so a use of decltype would save you defining one.
  • always the same type, (or dependent on template parameters in a way that is easy to express using existing type traits or similar) but the type is very long-winded to write and there is a much shorter and clear expression you can use instead.

So for example replacing std::iterator_traits<RandomAccessIterator>::value_type with decltype(*it) might well be a win, although auto does often handle such cases.

Subjective judgements enter at the point of "what is difficult", "what is long-winded" and "what is clear", but the rules of procedure can be the same regardless of how you make those judgements in specific cases.

auto, decltype(auto) and trailing return type

Trailing return type should only be used with auto

The point of decltype(auto) vs auto is to distinguish the case whether the return type should be a reference or value. But in your case the return type is already explicitly defined as decltype(std::get<0>(std::forward<T>(x))), so it will be perfectly-forwarded even if you use auto.

In auto f() -> T, the "auto" keyword is simply a syntactic construct to fill in a type position. It serves no other purpose.


In fact, in C++17 you cannot use a decltype(auto) with trailing-return-type together.

C++14 wordings (n3936 §7.1.6.4[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.

C++17 wordings (n4618 §7.1.7.4[dcl.spec.auto]/1):

The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later by deduction from an initializer. The auto type-specifier is also used to introduce a function type having a trailing-return-type or to signify that a lambda is a generic lambda (5.1.5). The auto type-specifier is also used to introduce a decomposition declaration (8.5).

This is DR 1852, see Does a placeholder in a trailing-return-type override an initial placeholder?.

Practically, while gcc accepts decltype(auto) f() -> T (which is a bug), but clang will reject it saying

error: function with trailing return type must specify return type 'auto',
not 'decltype(auto)'

use of decltype(auto) <func> before deduction of auto

Every source file (.cpp file or .cc file) has to stand on its own. The standard calls these files translation units, because the behaviour of the program is the same as-if all those files are translated into machine language as separate units, and only then the machine code is linked into one program.

Standing "on its own" means that the source file together with all files included in it have to convey all information to translate the source file. If you put the masterMethod in one source file and main in a different source file, the compiler has no idea which type is returned by masterMethod when compiling main.

The answer to your question

How would one seperate implementation and declaration as I would like to do it?

Is thus: Either you put the source code of the function in the header file, or you give up on using return type deduction. If you put the source code into the header file, you don't need to put it inside the class definition, as long as you declare it inline.

decltype(auto) vs auto&& to perform generic handling of function's return type

auto&& is already optimal for capturing function return values, such that the differences of decltype(auto) can only be disadvantages. In your example, lifetime extension is applied to the otherwise-temporary object returned from the function. This causes it to behave essentially the same as a directly named object, with the effect that the reference qualifier gets "erased."

Using decltype(auto) with a return-by-value function causes its return value object to be moved into the local. Depending what's inside the function, copy elision may apply which removes the distinction between the local and the temporary. But that only applied sometimes, whereas reference-bound lifetime extension is unconditional.

Even when applied, copy elision doesn't remove the requirement that the return object could be copied or moved. decltype(auto) can't initialize an object of non-movable type from a function return whereas auto && can, modulo the distinction between a local and a temporary with the lifetime of a local.

As it happens, that distinction can only be made by decltype, and it can only be made outside the local scope by decltype(auto). Since you usually want to treat lifetime-extended objects as locals, it is best to mind parentheses and std::decays when using decltype, and not to use decltype(auto) for function parameters (which is the most common application of auto &&).

What is the difference between returning auto&& and decltype(auto)?

What is the difference between decltype(auto) and auto&&?

decltype(auto) covers three cases. When returning lvalues, the return type would be T& (lvalue-reference); for xvalues, the return type would be T&& (rvalue-reference); for prvalues, the return type would be T (non-reference, i.e. return by-value).

auto&& covers only two cases. When returning lvalues, the return type would be T& (lvalue-reference); for rvalues, including xvalues and prvalues, the return type would be T&& (rvalue-reference). (Forwarding reference is always a reference.)

What happens if we return rvalue object, like: return int{};? Will return value be dangling reference?

For auto&& the return type is rvalue-reference, so yes, the returned reference is always dangling. For decltype(auto) the return type is non-reference then no such trouble.



Related Topics



Leave a reply



Submit