系列文章
C++11之正規表達式(regex_match、regex_search、regex_replace)
C++11之線程庫(Thread、Mutex、atomic、lock_guard、同步)
C++11之智能指針(unique_ptr、shared_ptr、weak_ptr、auto_ptr)淺談記憶體管理
C++11之強制類型轉換(static_cast,const_cast,dynamic_cast,reinterpret_cast)
C++11之Lanbda表達式(匿名函數)
C++11之右值引用:移動語義和完美轉發(帶你了解移動構造函數、純右值、将亡值、右值引用、std::move、forward等新概念)
C++11之委派構造函數
C++11之内聯名字空間(inline namespace)和ADL特性(Argument-Dependent name Lookup)
C++11之模闆的别名
C++11之一般化的SFINAE規則
C++11之auto類型推導
目錄
- 系列文章
- typeid與decltype
- decltype的使用場景
-
- 增加代碼的可讀性、簡潔性
- 擷取匿名自定義類型的類型
- 用于泛式程式設計
- 推導函數的傳回值類型
- decltype推導四規則
-
- 執行個體1
- 執行個體2
- 使用模闆來輔助推導識别
- cv限定符的繼承與備援的符号
typeid與decltype
在學習decltype之前,我們先了解一下
typeid
運算符。
typeid
運算符用來擷取一個表達式的類型資訊。需要包含
<typeinfo>
頭檔案才可以使用。
主要使用分為倆種場景:
- 對于基本類型(
、int
等float
内置類型)的資料,類型資訊所包含的内容比較簡單,主要是指資料的類型。C++
- 對于類類型的資料(對象),類型資訊是指對象所屬的類、所包含的成員、所在的繼承關系等。
我們可以通過
name
方法擷取到這個類型,通過
hash_code
方法傳回該類型唯一的哈希值(值得注意的是
hash_code
是在運作時擷取的資訊,
hash_code
也是
C++11
新加入的)。下面就通過
hash_code
方法判斷了倆個變量的類型是否一緻。
#include <iostream>
#include <typeinfo>
using namespace std;
class A{};
class B{};
int main()
{
A a;
B b;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
A c;
bool ret = (typeid(a).hash_code() == typeid(b).hash_code());
bool ret2 = (typeid(a).hash_code() == typeid(c).hash_code());
cout << "Same type?" << endl;
cout << "A and B?" << boolalpha << ret << endl;
cout << "A and C?" << boolalpha << ret2 << endl;
return 0;
}
運作結果:
class A
class B
Same type?
A and B?false
A and C?true
類型推導是用于模闆程式設計和泛式程式設計中的。因為在其他程式設計中,各類型的确定的,不需要類型推導。而在泛式程式設計中,類型就是未知的,例如下面這個例子,在編譯期T的類型是确定不了的。是以才引入了類型推導。最終定為了和
auto
,但是倆者功能并不相同。
decltype
template<typename T>
void test(T t)
{
;
}
decltype
的使用是非常簡單的。文法:
decltype(type) 變量名;
#include <typeinfo>
#include <iostream>
using namespace std;
int main()
{
int i = 1;
decltype(i) j = 0;
cout << typeid(j).name() << endl;
float a;
double d;
decltype(a + d) c;
cout << typeid(c).name() << endl;
return 0;
}
運作結果:
int
double
deletype以一個表達式或者變量為參數,然後傳回該表達式/變量的類型,是一個類型訓示符。decltype類型推導和auto自動類型一樣都是編譯期确定。
decltype的使用場景
增加代碼的可讀性、簡潔性
在使用疊代器時,每次都需要很長的疊代器類型,例如
map<int,int>::iterator
,使用decltype就可以将類型進行重定義,簡化代碼,提高可讀性。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> arr(10);
typedef decltype(arr.begin()) vecIter;
for(vecIter i = arr.begin(); i < arr.end(); ++i)
{
*i = 1;
}
for (decltype(arr)::iterator i = arr.begin(); i < arr.end(); ++i)
{
cout << *i << " ";
}
cout << endl;
return 0;
}
運作結果:
擷取匿名自定義類型的類型
在一般情況下,我們定義的匿名枚舉類型、匿名聯合體/共用體、匿名結構體,是無法二次建立變量的。但是有了decltype就可以實作上述的功能。
下面通過decltype類型訓示符通過匿名結構體數組變量推導出對應的匿名枚舉類型。
#include <iostream>
enum // 匿名枚舉
{
A,
B,
C,
}test;
union // 匿名聯合體/共用體
{
decltype(test) key;
char* name;
}test2;
struct // 匿名結構體數組
{
int d;
decltype(test2) id;
}test3[10];
int main()
{
decltype(test3) stu;
stu[0].id.key = decltype(test)::A; // 引用匿名強類型枚舉的值
return 0;
}
用于泛式程式設計
見下面這個例子,
Sum
函數模闆增加了一個類型為
decltype(t1 + t2)
的參數作為出參。這個出參的類型是根據
T1
和
T2
入參類型共同決定的。如果入參的類型是數組的話就需要為數組提供特殊的重載版本。
template<typename T1, typename T2>
void Sum(T1& t1, T2& t2, decltype(t1 + t2)& s)
{
s = t1 + t2;
}
void Sum(int a[], int b[], int c[])
{
// 數組版本
}
int main()
{
int a = 34;
long int b = 5;
float c = 1.0f;
float d = 2.3f;
long int e = 0;
float f = 0;
Sum(a, b, e);
Sum(c, d, f);
int arr1[5];
int arr2[5];
int arr3[5];
Sum(arr1, arr2, arr3);
return 0;
}
編譯
Sum(a,b,e)
時,因為入參的類型是
int
和
long int
是以,運算後出參類型就是
long int
。其他同理。
推導函數的傳回值類型
基于
decltype
的模闆類
result_of
,可以推導函數的傳回值。
#include <type_traits>
using namespace std;
typedef double (*func)();
int main()
{
result_of<func()>::type f = 1; // 推導函數的傳回值
std::cout << typeid(f).name() << std::endl;
return 0;
}
運作結果:
decltype推導四規則
編譯器在推導時會依照以下四個規則:
decltype(e)
- 如果e是一個沒有帶括号的**标記符表達式(id-expression)**或者類成員通路表達式,那麼
就是decltype(e)
所命名實體的類型。此外,如果e
是一個被重載的函數,則會導緻編譯時錯誤。e
- 否則,假設e的類型是T。如果e是一個将亡值(xvalue),那麼decltype(e)為T&&(右值引用)。
- 否則,假設e的類型是T。如果e是一個左值,則decltype(e)為T&(左值引用)。
- 否則,假設e的類型是T。則decltype(e)為T。
标記符表達式(id-expression):所有除了關鍵字、字面量等編譯器需要使用的标記之外的程式員自己定義的标記(token)都可以是标記符(identifier)。單個标記符對應的表達式就是标記符表達式。例如
int arr[2];
這裡的
arr
就是标記符表達式,而
arr[1]
、
arr[1] + 1
都不是标記符表達式。
執行個體1
了解了推導規則後,我們來練習一個吧。
int main()
{
int i;
decltype(i) a; // a type: int
decltype((i)) b; // b type: int& 編譯失敗
return 0;
}
先來分析
decltype(i) a;
:
i
是一個标記符表達式,是以推導的類型就是實體的類型,也就是
int
。
再來分析
decltype((i)) b;
:
(i)
不是一個标記符表達式,是以第一條不成立,
(i)
是一個左值,是以符合規則第三條,那麼推導的類型為
int&
。
執行個體2
經過前面的簡單練習,我相信應該對這個規則有一定的認知了。那麼我們就在分析一個稍微複雜的程式。
定義下面變量以及函數,用于之後的分析:
int i = 4;
int arr[5] = { 0 };
int* ptr = arr;
struct S
{
double d;
S():d(0){}
}s;
void Test();
// 重載
void Overloaded(int);
void Overloaded(char);
int&& RvalRef();
const bool Func(int);
規則1 單個标記符表達式以及通路類成員變量 均為推導為原本類型
推導 | 說明 |
---|---|
decltype(arr) var1; | int[5] 标記符表達式 |
decltype(ptr) var2; | int& 标記符表達式 |
decltype(Test) var3; | void __cdecl(void) 标記符表達式 |
decltype(s.d) var4; | double 成員通路表達式 |
decltype(Overloaded) var5; | 編譯失敗 不支援重載版本的函數 |
規則2 将亡值 推導為類型的右值引用 &&
推導 | 說明 |
---|---|
decltype(RvalRef()) var6 = 1; | int&& |
規則3 左值 推導為類型的引用
推導 | 說明 |
---|---|
decltype(true ? i : 11) var7 = i; | int& 三目運算符 這裡會傳回一個int類型 |
decltype((i)) var8 = i; | int& 帶括号的左值 |
decltype(++i) var9 = i; | int& ++i 傳回i的左值 |
decltype(arr[3]) var10 = i; | int& []下标運算符會傳回左值 |
decltype(*ptr) var11 = i; | int& *ptr <=> arr[0] 傳回左值 |
decltype(“lval”) var12 = “lval”; | const char(&)[5] 字元串字面常量 屬于左值 |
規則4 以上都不是,就推導為自身類型
推導 | 說明 |
---|---|
decltype(1) var13; | int 除了字元串外的字面常量均為右值 |
decltype(i++) var14; | int i++傳回右值 |
decltype(Func(i)) var15; | const bool 傳回右值 |
使用模闆來輔助推導識别
我們可以通過C++11的
is_lvalue_reference
、
is_rvalue_reference
,可以幫助我們對一些推導結果的識别。
#include <type_traits>
#include <iostream>
int main()
{
int i = 4;
int arr[5] = { 0 };
int* ptr = arr;
int&& RvalRef();
// 是右值引用嗎?
std::cout << std::is_rvalue_reference<decltype(RvalRef())>::value << std::endl;
std::cout << std::is_rvalue_reference<decltype(i++)>::value << std::endl;
// 是左值引用嗎?
std::cout << std::is_lvalue_reference<decltype(true ? i : i)>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype((i))>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype(i)>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype(++i)>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype(arr[0])>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype(*ptr)>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype("lval")>::value << std::endl;
std::cout << std::is_lvalue_reference<decltype(i++)>::value << std::endl;
return 0;
}
cv限定符的繼承與備援的符号
在定義變量/對象有被
const
和
volatile
限定符修飾,在使用
decltype
進行推導時,其成員不會繼承
const
和
volatile
限定符。
is_const
用來判斷是否被
const
限定符修飾,
is_volatile
用來判斷是否被
volatile
修飾。
#include <type_traits>
#include <iostream>
using std::cout;
using std::endl;
using std::is_const;
using std::is_volatile;
int main()
{
const int c = 0;
volatile int v = 0;
struct S
{
int i;
};
const S a = { 0 };
volatile S b;
volatile S* p = &b;
cout << is_const<decltype(c)>::value << endl; // 1
cout << is_volatile<decltype(v)>::value << endl; //1
cout << is_const<decltype(a)>::value << endl;//1
cout << is_volatile<decltype(b)>::value << endl;//1
cout << is_const<decltype(a.i)>::value << endl;//0
cout << is_volatile<decltype(p->i)>::value << endl;//0
return 0;
}
decltype在表達式的推導中,如果遇到一個備援的符号将會被優化掉。
#include <type_traits>
#include <iostream>
using std::cout;
using std::endl;
using std::is_lvalue_reference;
using std::is_rvalue_reference;
int main()
{
int i = 1;
int& j = i;
int* p = &i;
const int k = 1;
decltype(i)& var1 = i; // 左值引用
decltype(j)& var2 = i; // 備援的& 将會被優化掉 左值引用
cout << is_lvalue_reference<decltype(var1)>::value << endl; // 1
cout << is_rvalue_reference<decltype(var2)>::value << endl; // 0
cout << is_lvalue_reference<decltype(var2)>::value << endl; // 1
decltype(p)* var3 = &i; // 編譯失敗
decltype(p)* var4 = &p; // int**
auto* v3 = p; // int*
v3 = &i;
const decltype(k) var5 = 1; // const備援 将會被優化掉
return 0;
}
這裡特别要注意的是
decltype(p)*
的情況。可以看到,在定義
var4
變量的時候,由于
p
的類型是
int*
,是以
var3
被定義為了
int**
類型。這跟
auto
聲明中,·也可以是備援的不同。在
decltype
後的
*
号,并不會被編譯器忽略。
此外我們也可以看到,
var4
中
const
可以被備援的聲明,這就會被優化掉,同樣的
volatilc
限制符也會如此。
總的說來,算得上是
decltype
中類型推導使用方式上最靈活的一種。雖然看起來它的推導規則比較複雜,有的時候跟
C++11
推導結果還略有不同,但大多數時候,我們發現使用
auto
還是自然而親切的。一些細則的差別,讀者可以在使用時遇到問題再傳回查驗。而下面的追蹤傳回類型的函數定義,則将融合
decltype
、
auto
,将
decltype
中的泛型能力提升到更高的水準。
C++11