C++ decltype type derivation

Posted by Unipus on Sun, 27 Feb 2022 01:34:27 +0100



Both auto and decltype keywords can automatically deduce the type of variable, but their usage is different:

auto varname = value;
decltype(exp) varname = value;

Where varname represents the variable name, value represents the value assigned to the variable, and exp represents an expression.

auto deduces the type of the variable according to the initial value value on the right of = while decltype deduces the type of the variable according to the exp expression, which has nothing to do with the value on the right of = 2.

In addition, auto requires variables to be initialized, while decltype does not. It is easy to understand that auto deduces the variable type according to the initial value of the variable. If it is not initialized, the variable type cannot be deduced. Decltype can be written in the following form:

decltype(exp) varname;

exp precautions
In principle, exp is an ordinary expression, which can be in any complex form, but we must ensure that the result of exp is typed, not void; For example, when exp calls a function whose return value type is void, the result of exp is also void, which will lead to a compilation error.

Examples of C++ decltype usage:

int a = 0;
decltype(a) b = 1;  //b is derived as int
decltype(10.8) x = 5.5;  //x is derived as double
decltype(x + 100) y;  //y is derived as double

It can be seen that decltype can deduce the type of variable according to variable, literal and expression with operator. Please note that line 4, y is not initialized.

decltype derivation rule
When a programmer uses decltype(exp) to get a type, the compiler will get the result according to the following three rules:

If exp is an expression that is not surrounded by parentheses (), or a class member access expression, or a separate variable, the type of decltype(exp) is the same as exp, which is the most common case.

If exp is a function call, the type of decltype(exp) is the same as that of the return value of the function.

If exp is an lvalue or surrounded by parentheses (), the type of decltype(exp) is the reference of exp; Assuming that the type of exp is T, the type of decltype(exp) is T&.

[example 1] exp is a common expression:

#include <string>
using namespace std;
class Student{
    static int total;
    string name;
    int age;
    float scores;
int Student::total = 0;
int  main(){
    int n = 0;
    const int &r = n;
    Student stu;
    decltype(n) a = n;  //n is of type int, and a is deduced to be of type int
    decltype(r) b = n;     //r is const int & type, and b is derived as const int & type
    decltype(Student::total) c = 0;  //total is a member variable of type int of class Student, and c is deduced as type int
    decltype(stu.name) url = "http://c.biancheng. Net / cplus / "; / / total is a string type member variable of class Student, and url is deduced as string type
    return 0;

[example 2] exp is a function call:

//Function declaration
int& func_int_r(int, char);  //The return value is int&
int&& func_int_rr(void);  //The return value is int&&
int func_int(double);  //The return value is int
const int& fun_cint_r(int, int, int);  //The return value is const int&
const int&& func_cint_rr(void);  //The return value is const int&&
//decltype type derivation
int n = 100;
decltype(func_int_r(100, 'A')) a = n;  //The type of a is int&
decltype(func_int_rr()) b = 0;  //The type of b is int&&
decltype(func_int(10.5)) c = 0;   //The type of c is int
decltype(fun_cint_r(1,2,3))  x = n;    //The type of x is const int&
decltype(func_cint_rr()) y = 0;  // The type of y is const int&&

It is important to note that calling functions in exp requires parentheses and parameters, but this is just a form and does not really execute function codes.

[example 3] exp is an lvalue or surrounded by ():

using namespace std;
class Base{
    int x;
int main(){
    const Base obj;
    //Parenthesized expression
    decltype(obj.x) a = 0;  //obj.x is the member access expression of the class, which complies with derivation rule 1. The type of a is int
    decltype((obj.x)) b = a;  //obj.x is in parentheses and conforms to derivation rule 3. The type of b is int&.
    //additive expression 
    int n = 0, m = 0;
    decltype(n + m) c = 0;  //n+m gets a right value, which conforms to the derivation rule 1, so the derivation result is int
    decltype(n = n + m) d = c;  //n=n+m get an lvalue, sign derivation rule 3, so the derivation result is int&
    return 0;

Here we need to focus on the left value and right value: the left value refers to the data that still exists after the execution of the expression, that is, persistent data; The right value refers to the data that no longer exists after the execution of the expression, that is, temporary data. There is a very simple way to distinguish between lvalues and rvalues. Take the address of the expression. If the compiler does not report an error, it will be lvalue, otherwise it will be rvalue.

Practical application of decltype

The syntax format of auto is simpler than decltype, so it is more convenient to use auto than decltype in general type derivation.

We know that auto can only be used for static members of a class, not for non static members of a class (ordinary members). If we want to deduce the type of non static members, we must use decltype at this time. The following is the definition of a template:

#include <vector>
using namespace std;
template <typename T>
class Base {
    void func(T& container) {
        m_it = container.begin();
    typename T::iterator m_it;  //Pay attention here
int main()
    const vector<int> v;
    Base<const vector<int>> obj;
    return 0;

Look at m in the Base class alone_ It is difficult to see what errors will occur in the definition of it members, but when using the Base class, if a const type container is passed in, the compiler will immediately pop up a lot of error messages. The reason is that T::iterator cannot include all iterator types. When T is a const container, const should be used_ iterator.

To solve this problem, in the previous version of C++98/03, we can only find a way to treat const type containers separately with template specialization, which increases a lot of workload and looks very obscure. However, with the decltype keyword of C++11, it can be written as follows:

template <typename T>
class Base {
    void func(T& container) {
        m_it = container.begin();
    decltype(T().begin()) m_it;  //Pay attention here

Does it look refreshing?

Topics: C++ Back-end