Understand decltype keyword

1. decltype keyword

decltype is called a type specifier. Its function is to select and return the data type of the operand.
for example
The return value of the Test2 function is std::initializer_list type

std::initializer_list<int> Test2()
{
     return { 1,2,3 };
}

We use the consistent initial list

decltype(Test2()) Arr1 = { 1,2,3 };

Can run, so the decltype keyword gets the type of the return value of Test2

2. More general examples

const int i = 0; // decltype(i) is const int

bool f(const Widget& w); // decltype(w) is const Widget&
 // decltype(f) is bool(const Widget&)
 
struct Point 
{
 int x, y; // decltype(Point::x) is int
}; // decltype(Point::y) is int

Widget w; // decltype(w) is Widget

if (f(w)) ... // decltype(f(w)) is bool

template<typename T> // simplified version of std::vector
class vector 
{
public:
 ...
 T& operator[](std::size_t index);
 ...
};

vector<int> v; // decltype(v) is vector<int>
...
if (v[0] == 0) ... // decltype(v[0]) is int&

3.C11

In C++11, the main purpose of this keyword is that you declare a function template whose return value depends on the formal parameter type.

template<typename Container, typename Index> // works, but
auto authAndAccess(Container& c, Index i) // requires
 -> decltype(c[i]) // refinement
{
 
 return c[i];
}

be careful
① auto in the above code does not represent automatic type derivation, it only represents a syntax
Indicates that the syntax attribute of C++11 tail return type is being used
② The advantage of tail return type is that the parameters of the function can be used to specify the return type

4. C14

C++11 allows type derivation of lambda expressions of monolingual sentences. In C++14, it is extended to all Lambdas and all functions, including multiple statements (or even multiple returns, providing the same derivation type for all outputs). This means that in C++14, we can ignore the tail return type and leave only auto

template<typename Container, typename Index> // C++14;
auto authAndAccess(Container& c, Index i) // not quite
{ // correct
 
 return c[i]; // return type deduced from c[i]
}

① Using this form, auto does have type derivation
② This means that the compiler will infer the return type of the function from the implementation of the function
The problem is that the compiler infers from auto
According to the previous terms, what will be inferred?
Only its value will be returned, that is, it is an R-value

std::deque<int> d;
...
authAndAccess(d, 5) = 10; // authenticate user, return d[5],
 // then assign 10 to it;
 // this won't compile!

So how to change this?

template<typename Container, typename Index> // C++14; works,
decltype(auto) // but still
authAndAccess(Container& c, Index i) // requires
{ // refinement
 authenticateUser();
 return c[i];
}

5. decltype(auto)

decltype(auto) is not limited to the return value of a function, but can also be used to declare variables

Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction:
// myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction:
 // myWidget2's type is
 // const Widget&

6. Reanalysis

authAndAccess(Container& c, Index i)

The customer may just want to get a copy

std::deque<std::string> makeStringDeque(); // factory function
// make copy of 5th element of deque returned
// from makeStringDeque
auto s = authAndAccess(makeStringDeque(), 5);

The template cannot pass in an R-value for c, because c is a reference, so the above template still needs to be improved

template<typename Container, typename Index> // c is now a
decltype(auto) authAndAccess(Container&& c, // universal
 Index i); // reference

Now that c is a general reference, you can pass in an R-value or an l-value for c
c is now a generic reference. In this template, we do not know the type of container being operated on, which means that we are also ignorant of the type of index object it uses. In order to make it meet various situations, we need to modify the code again:

template<typename Container, typename Index> // final
decltype(auto) // C++14
authAndAccess(Container&& c, Index i) // version
{
 authenticateUser();
 return std::forward<Container>(c)[i];
}

C++11 can be written like this

template<typename Container, typename Index> // final
auto // C++11
authAndAccess(Container&& c, Index i) // version
-> decltype(std::forward<Container>(c)[i])
{
 authenticateUser();
 return std::forward<Container>(c)[i];
}

7.decltype(x) and decltype((x))

The lvalue is derived as a reference by decltype

int x = 0;
(x);

(x) Will be treated as an lvalue

decltype(auto) f1()
{
 int x = 0;
 ...
 return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
 int x = 0;
 ...
 return (x); // decltype((x)) is int&, so f2 returns int&
 }

f2 returns a reference to a local variable, which is a disaster for C-series programmers, QAQ

8. Attention


Second point

int nNum1 = 10;

decltype(auto)d = nNum1 = 20;

Lvalue expression, so d is derived as int&

nNum1 is not an expression, but a variable name

decltype(auto)d = nNum1;

d is int

Keywords: C++

Added by AV1611 on Wed, 12 Jan 2022 03:51:06 +0200