[From]
Author: Su Fang
Links: https://subingwen.cn/cpp/autotype/
Source: Big C who likes programming
Many new features have been added to C++11, such as the ability to use auto to automatically derive the type of variable and the ability to combine decltype to represent the return value of a function. Using new features allows us to write simpler, more modern code.
1. auto
In C++98, auto and static correspond, indicating that variables are automatically stored, but non-static local variables are automatically stored by default, so this keyword has become very cocktail. In C++11, they give new meaning, using this keyword can automatically deduce the actual type of variables like other languages.
1.1 Derivation Rule
In C++11, auto does not represent an actual data type, it is just a placeholder for type declaration. Auto does not always deduce the actual type of a variable in any scenario. Variables declared with auto must be initialized so that the compiler can deduce its actual type and replace the auto placeholder with the real type at compile time. The usage syntax is as follows:
auto Variable Name = Variable Value;
Based on the above grammar, here are some simple examples:
auto x = 3.14; // x is floating point auto y = 520; // y is a reshaping int auto z = 'a'; // z is a character char auto nb; // error, variable must be initialized auto double nbl; // Syntax error, cannot modify data type
Limitations of 1.2 auto
The auto keyword is not omnipotent, and type inference cannot be accomplished in these scenarios:
Limit 1: Cannot be used as a function parameter. Since arguments are only passed to function parameters when a function is called, auto requires that the modifier variable be assigned a value, so the two contradict.
int func(auto a, auto b) // ERROR: a parameter cannot have a type that contains 'auto' { std::cout << "a: " << a <<", b: " << b << std::endl; }
Limit 2: Cannot be used for initialization of non-static member variables of a class
class Test { auto v1 = 0; // ERROR: 'v1': a non-static data member cannot have a type that contains 'auto' static auto v2 = 0; // ERROR: 'Test::v2': a static data member with an in - class initializer must have non - volatile const integral type or be specified as 'inline' static const auto v3 = 10; // ok }
Limit 3: Arrays cannot be defined using the auto keyword
int func() { int array[] = {1,2,3,4,5}; // Define Array auto t1 = array; // ok, t1 is deduced as int*type auto t2[] = array; // error, auto cannot define array auto t3[] = {1,2,3,4,5}; // error, auto cannot define array }
Limit 4: Template parameters cannot be derived using auto
template <typename T> struct Test{} int func() { Test<double> t; Test<auto> t1 = t; // error, cannot deduce template type return 0; }
1.3 Application of Auto
After defining an stl container before C++11, this code is often written when traversing:
#include <map> int main() { std::map<int, std::string> person; std::map<int, std::string>::iterator it = person.begin(); for (; it != person.end(); ++it) { // do something } return 0; }
You can see that the code is very long when defining the iterator variable it, which is cumbersome to write and refreshes a lot after using auto:
#include <map> int main() { std::map<int, std::string> person; // Code Simplification 1 for (auto it = person.begin(); it != person.end(); ++it) { // do something } // Code Simplification 2 for (auto it : person) { // do something } return 0; }
2. decltype
In some cases, you don't need or can't define variables, but you want to get a type. Then you can use the decltype keyword provided by C++11, which is used to derive the type of an expression when the compiler compiles. The syntax format is as follows:
decltype (Expression)
Decltype is short for declare type, meaning declare type. The decltype derivation is done at compile time. It is only for the derivation of expression types and does not calculate the value of expressions. Let's look at a simple set of examples:
int a = 10; decltype(a) b = 99; // b -> int decltype(a+3.14) c = 52.13; // c -> double decltype(a+b*c) d = 520.1314; // d -> double
You can see that the decltype deduced expression can be simple and complex. At this point auto cannot do it. Auto can only deduce the initialized variable type.
2.1 Derivation Rules
Scenario 1: The expression is a common variable or expression or a class expression, in which case the type derived using decltype is identical to the type of expression.
#include <iostream> #include <string> class Test { public: std::string text; static const int value = 110; }; int main() { int x = 99; const int &y = x; decltype(x) a = x; decltype(y) b = x; decltype(Test::value) c = 0; Test t; decltype(t.text) d = "hello, world"; return 0; }
- Variable a is deduced to be of type int
- Variable b is derived as const int &type
- Variable c is deduced to be of type const int
- Variable d is derived as std::string type
Scenario 2: The expression is a function call, and the type inferred using decltype matches the return value of the function.
class Test{...}; //Function declaration int func_int(); // Return value is int int& func_int_r(); // The return value is int& int&& func_int_rr(); // The return value is int && const int func_cint(); // Return value is const int const int& func_cint_r(); // The return value is const int& const int&& func_cint_rr(); // The return value is const int && const Test func_ctest(); // Return value is const Test //decltype type derivation int n = 100; decltype(func_int()) a = 0; decltype(func_int_r()) b = n; decltype(func_int_rr()) c = 0; decltype(func_cint()) d = 0; decltype(func_cint_r()) e = n; decltype(func_cint_rr()) f = 0; decltype(func_ctest()) g = Test();
- Variable a is deduced to be of type int
- Variable b is deduced as int&type
- Variable c is deduced as int&type
- Variable d is deduced to be of type int
- Variable e is derived as const int &type
- Variable f is derived as const int&type
- Variable g is derived as const Test type
Function func_cint () returns a pure right value (data that no longer exists after the expression is executed, that is, temporary data). For pure right values, only class types can carry const and volatile qualifiers. In addition, these two qualifiers need to be ignored, so the derived variable d is of type int instead of const int.
Scenario 3: An expression is a left value, or surrounded by parentheses (), and a reference to the expression type is derived using decltype (const, volatile qualifiers cannot be ignored if any).
#include <iostream> #include <vector> class Test { public: int num; }; int main() { const Test obj; //Parenthesized expression decltype(obj.num) a = 0; decltype((obj.num)) b = a; //additive expression int n = 0, m = 0; decltype(n + m) c = 0; decltype(n = n + m) d = n; return 0; }
- obj.num is a member access expression for the class, which conforms to Scenario 1, so a is of type int
- obj.num is parenthesized and conforms to scene 3, so b is of type const int&
- n+m gets a right value that matches Scene 1, so c is of type int
- n=n+m gets a left value n, which corresponds to scene 3, so d is of type int&
2.2 Application of decltype
Applications of decltype are often found in generic programming. For example, we write a class template that adds a function to traverse containers, as follows:
#include <list> template <class T> class Container { public: void func(T& c) { for (m_it = c.begin(); m_it != c.end(); ++m_it) { std::cout << *m_it << " "; } std::cout << std::endl; } private: ??? m_it; // The iterator type cannot be determined here }; int main() { const std::list<int> lst; Container<const std::list<int>> obj; obj.func(lst); return 0; }
M_in program It has a problem here. There are two types of iterator variables: read-only (T:: const_iterator) and read-write (T::iterator). With decltype, you can solve this problem perfectly. When T is a non-const container, you get a T::iterator, and when T is a const container, you get a T::const_iterator. Iterator.
#include <list> #include <iostream> template <class T> class Container { public: void func(T& c) { for (m_it = c.begin(); m_it != c.end(); ++m_it) { std::cout << *m_it << " "; } std::cout << std::endl; } private: decltype(T().begin()) m_it; // Successfully deduce T type using decltype }; int main() { const std::list<int> lst{ 1,2,3,4,5,6,7,8,9 }; Container<const std::list<int>> obj; obj.func(lst); return 0; }
3. Postposition of return type
In generic programming, you may need to calculate parameters to get the type of return value, such as the following scenario:
#include <iostream> // R->Return Value Type, T->Parameter 1 Type, U->Parameter 2 Type template <typename R, typename T, typename U> R add(T t, U u) { return t + u; } int main() { int x = 520; double y = 13.14; // auto z = add<decltype(x + y), int, double>(x, y); auto z = add<decltype(x + y)>(x, y); // Simplified Writing std::cout << "z: " << z << std::endl; return 0; }
As for the return value, it can be inferred from the code above that the result type of the expression t+u is the same, so it can be inferred from decltype, and the parameters T and U of the template function can be automatically inferred from the actual parameters, so they can also be omitted from the program. Although the above approach solves the problem, the solution is a bit too idealized because the caller does not know what kind of processing is performed inside the function.
So if you want to solve this problem, you have to write directly on the add function. First, let's look at the first way:
template <typename T, typename U> //ERROR: error C2065: 't': undeclared identifier decltype(t+u) add(T t, U u) { return t + u; }
When we change these lines of code in the compiler, we get a direct error, so t and u in the decltype are both function parameters, which directly corresponds to variables being used without defining them.
In C++11, a post-type syntax for return types is added, which means that decltype and auto are combined to complete the derivation of return types. Its grammatical format is as follows:
// Symbol - > is followed by the type of function return value auto func(Parameter 1, Parameter 2, ...) -> decltype(Parameter expression)
By analyzing the syntax code behind the return type mentioned above, it is concluded that auto tracks the type decltype() derives, so the add() function above can be modified as follows:
#include <iostream> template <typename T, typename U> // Return type postposition syntax auto add(T t, U u) -> decltype(t+u) //OK { return t + u; } int main() { int x = 520; double y = 13.14; // auto z = add<int, double>(x, y); auto z = add(x, y); // Simplified Writing std::cout << "z: " << z << std::endl; return 0; }