何谓decltype
decltype
是“declare type
”的缩写,译为“声明类型”。
decltype
说明符是c++ 11
引入的另一个新特性。decltype
检查实体的声明类型,或表达式的类型和值类别。
语法如下:
decltype ( 实体 ) (1) (C++11 起)
decltype ( 表达式 ) (2) (C++11 起)
推导规则说明:
规则1
- 如果实参是没有括号的标识表达式或没有括号的类成员访问表达式,那么 decltype 产生以该表达式命名的实体的类型。如果没有这种实体或该实参指名了一组重载函数,那么编译错误。
- 示例1
#include <vector>
int main() {
int x = 42;
std::vector<decltype(x)> v(100, x); // v 是一个 vector<int>
}
生成的代码如下
#include <vector>
int main(){
int x = 42;
std::vector<decltype(x)> v = std::vector<int, std::allocator<int> >(100, x, std::allocator<int>());
return 0;
}
- 示例2
struct S {
int x = 42;
};
const S s;
decltype(s.x) y; // Y的类型是int,尽管s.x是const
生成的代码如下
struct S{
int x;
// inline constexpr S() noexcept = default;
};
int main(){
const S s = S();
int y;
return 0;
}
- 示例3
int func(){
return 0;
}
int func(int a){
return 0;
}
int main(){
int i = 4;
// 不正确的使用。Func为重载函数命名
decltype(func) var1;
// 正确的使用。重载操作没有歧义
decltype(func(i)) var2;
return 0;
}
规则2
- 如果实参是其他类型为 T 的任何表达式,且
- 如果 表达式 的值类别是亡值,将会
decltype
产生T&&
;- 如果 表达式 的值类别是左值,将会
decltype
产生T&
;- 如果 表达式 的值类别是纯右值,将会
decltype
产生T
。该类型不需要是完整类型或拥有可用的析构函数,而且类型可以是抽象的。此规则不适用于其子表达式:decltype(f(g()))
中,g()
必须有完整类型,但f()
不必。
注意:如果对象的名字带有括号,那么它会被当做通常的左值表达式,从而
decltype(x)
和decltype((x))
通常是不同的类型。在难以或不可能以标准写法进行声明的类型时,decltype
很有用,例如lambda
相关类型或依赖于模板形参的类型。
示例1
int f() { return 42; }
int& g() { static int x = 42; return x; }
int main() {
int x = 42;
decltype(f()) a = f(); // a的类型是int
decltype(g()) b = g(); // b的类型是int&
decltype((x)) c = x; // c的类型是int&,因为x是左值
}
生成的代码如下:
int f(){ return 42;}
int & g(){
static int x = 42;
return x;
}
int main(){
int x = 42;
int a = f();
int & b = g();
int & c = x;
return 0;
}
综合示例:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
struct A { double x; };
int&& f1(void); //返回值为 int&&
int main() {
int var = 1;
const A *a = new A();
decltype(f1()) a1 = 0 ;
decltype(var) a2 ;
decltype(a->x) a3;
decltype((a->x)) a4 =11;
}
编译生成代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
struct A
{
double x;
};
int && f1();
//返回值为 int&&
int main()
{
int var = 1;
const A * a = new A();
int && a1 = 0;//decltype(f1()) a1 = 0 ;
int a2;//decltype(var) a2 ;
double a3;//decltype(a->x) a3;
const double & a4 = 11;//decltype((a->x)) a4 =11;
return 0;
}
decltype | 类型 | 说明 |
---|---|---|
decltype(f1()) a1 | const int&& | 一个指向const int对象的右值引用。 |
decltype(var); | int | 变量var的类型。 |
decltype(a->x); | double | 成员访问的类型。 |
decltype((a->x)) a4 | const double& | 内部圆括号导致语句被作为表达式而不是成员访问进行计算。由于a 被声明为const 指针,该类型是const double 类型的引用。 |
应用
应用1
c++11中,比较典型的应用示例是decltype
与type\using
的合用。
using size_t = decltype(sizeof(0));
using ptrdiff_t = decltype((int*)0-(int*)0);
using nullptr_t = decltype(nullptr);
应用2
- c++11
template<typename T, typename U>
auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }
int main() {
int x = 42;
double y = 77.22;
add(x,y);
}
生成代码如下:
template<typename T, typename U>
auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }
/* First instantiated from: insights.cpp:8 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
double add<int &, double &>(int & t, double & u)
{
return static_cast<double>(t) + u;
}
#endif
int main()
{
int x = 42;
double y = 77.219999999999999;
add(x, y);
return 0;
}
- c++14
template<typename T, typename U>
decltype(auto) add1(T&& t, U&& u){ return t + u; }
int main() {
int x = 42;
double y = 77.22;
add1(x,y);
}
生成的代码如下:
template<typename T, typename U>
decltype(auto) add1(T&& t, U&& u){ return t + u; }
#ifdef INSIGHTS_USE_TEMPLATE
template<>
double add1<int &, double &>(int & t, double & u){
return static_cast<double>(t) + u;
}
#endif
int main(){
int x = 42;
double y = 77.219999999999999;
add1(x, y);
return 0;
}
书中示例研习
缺陷代码1
template<typename T, typename U>
auto authAndAcess(T &t,U u){
return t[u];
}
此代码调用authAndAcess(d,5)
后推导代码如下:
template<>
int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
return t.operator[](u);
}
原因为:auto关键字进行类别推导时会忽略引用特性,因此虽然d[5]返回的时int &,但是最终却是int的结果。因此无法执行authAndAcess(d,5)=10
缺陷代码2
//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
return t[u];
}
此代码调用authAndAcess(d,5)
后推导代码如下:
template<>
int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
return t.operator[](u);
}
可以实现赋值了,但是形参第一个无法接收右值
c++14的最终版本如下
#include <iostream>
#include <deque>
using namespace std;
//推导为int ,无法对返回值赋值
template<typename T, typename U>
auto authAndAcess(T &t,U u){
return t[u];
}
//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
return t[u];
}
//c++ 14的最终版本
template<typename T, typename U>
decltype(auto)
authAndAcess2(T &&t,U u){
return std::forward<T>(t)[u];
}
std::deque<std::string> makeStringDeque(){
return {"1","2","3","4","5"};
}
int main() {
std::deque<int> d={0,1,2,3,4,5,6,7};
authAndAcess(d,5);
authAndAcess1(d,5)=10;
//note: candidate function [with T = std::deque<std::basic_string<char>>, U = int] not viable: expects an lvalue for 1st argument
//authAndAcess1(makeStringDeque(),2);//第一个参数是左值,不能传入右值
auto s = authAndAcess2(makeStringDeque(),5);
authAndAcess2(d,5)= 12;
//std::deque<std::string> makeStringDeque();
}
编译后代码生成的代码如下:
#include <iostream>
#include <deque>
using namespace std;
//推导为int ,无法对返回值赋值
template<typename T, typename U>
auto authAndAcess(T &t,U u){
return t[u];
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
return t.operator[](u);
}
#endif
//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
return t[u];
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
return t.operator[](u);
}
#endif
//c++ 14的最终版本
template<typename T, typename U>
decltype(auto) authAndAcess2(T &&t,U u){
return std::forward<T>(t)[u];
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> & authAndAcess2<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >, int>(std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > && t, int u)
{
return std::forward<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > >(t).operator[](static_cast<unsigned long>(u));
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int & authAndAcess2<std::deque<int, std::allocator<int> > &, int>(std::deque<int, std::allocator<int> > & t, int u)
{
return std::forward<std::deque<int, std::allocator<int> > &>(t).operator[](static_cast<unsigned long>(u));
}
#endif
std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > makeStringDeque()
{
return std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >{std::initializer_list<std::basic_string<char> >{std::basic_string<char>(std::basic_string<char>("1", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("2", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("3", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("4", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("5", std::allocator<char>()))}, std::allocator<std::basic_string<char> >()};
}
int main()
{
std::deque<int> d = std::deque<int, std::allocator<int> >{std::initializer_list<int>{0, 1, 2, 3, 4, 5, 6, 7}, std::allocator<int>()};
authAndAcess(d, 5);
authAndAcess1(d, 5) = 10;
std::basic_string<char> s = std::basic_string<char>(authAndAcess2(makeStringDeque(), 5));
authAndAcess2(d, 5) = 12;
return 0;
}