考慮以下用于型別推導情況的偽代碼:
template<typename T> void f(ParamType param);
呼叫函式將是:f(expr);
根據型別推導情況,ParamType 不是參考、指標或通用參考(參見 S. Meyers “Effective Modern C ”, p.14),而是通過值傳遞,以確定型別 T,首先需要忽略'expr' 的參考和 const 部分,然后模式匹配 expr 型別以確定 T。
司機將是:
void PerformTest() {
int i = 42;
int* pI = &i;
f_const_left(pI);
f_non_template_left(pI);
f_const_right(pI);
f_non_template_right(pI);
}
現在考慮這些函式,使用此推導規則,在使用指標作為引數呼叫時顯示出一些違反直覺的結果:
template<typename T> void f_const_left(const T t) {
// If 'expr' is 'int *' then, according to deduction rule for value parameter (Meyers p. 14),
// we need to get rid of '&' and 'const' in exp (if they exist) to determine T, thus T will be 'int *'.
// Hence, ParamType will be 'const int *'.
// From this it follows that:
// 1. This function is equivalent to function 'func(const int * t){}'
// 2. If ParamType is 'const int *' then we have non-const pointer to a const object,
// which means that we can change what pointer points to but cant change the value
// of pointer address using operator '*'
*t = 123;// compiler shows no error which is contradiction to ParamType being 'const int *'
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// As we see, consequence 2. is not satisfied:
// T is straight opposite: instead of being 'const int *'
// T is 'int const *'.
// So, the question is:
// Why T is not 'const int*' if template function is f(const T t) for expr 'int *' ?
}
考慮后果1:
讓我們創建一個等效的非模板函式:
void f_non_template_left(const int* t) {
// 1. Can we change the value through pointer?
*t = 123; // ERROR: expression must be a modifiable lvalue
// 2. Can we change what pointers points to?
t = nullptr; // NO ERROR
// As we can see, with non-template function situation is quite opposite.
}
為了實驗的完整性,讓我們還考慮另一對函式,但將“const”放在 T 的右側:一個模板函式及其非模板等效項:
template<typename T> void f_const_right(T const t) {
// For expr being 'int *' T will be 'int *' and ParamType will be 'int * const',
// which is definition of a constant pointer, which cant point to another address,
// but can be used to change value through '*' operator.
// Lets check it:
// Cant point to another address:
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// Can be used to change its value:
*t = 123;
// So, as we see, in case of 'T const t' we get 'int * const' which is constant pointer, which
// is intuitive.
}
最后,型別右側帶有“const”的非模板函式:
void f_non_template_right(int* const t) {
// 1. Can we change the value through pointer?
*t = 123; // No errors
// 2. Can we change what pointers points to?
t = nullptr; // ERROR: you cant assign to a variable that is const
// As we can see, this non-template function is equivalent to its template prototype
}
有人能解釋一下為什么模板函式和非模板函式之間存在如此不一致的地方嗎? 為什么左側帶有“const”的模板函式不符合演繹規則?
uj5u.com熱心網友回復:
// 因此,ParamType 將是 'const int *'。
不,它不會(不僅僅是因為ParamType代碼中沒有使用它。為了這個答案的目的,讓我們假設你的意思是T,它在那里)。
int *是兩個標記的序列。這些標記之一是型別名 ( int),另一個是*. 當以這種方式組合時,這兩個標記命名為單一型別:指向int.
const int是兩個標記的序列。組合時,它們將型別命名為: const int。
const int*是 3 個標記的序列。當這樣組合時,它們命名為單一型別。C 型別命名的規則是const將“const-ness”應用于緊鄰其左側的任何型別指定的型別。如果它的左邊沒有任何東西,它適用于緊鄰右邊的東西。因此,如果您將其視為運算式,則將其讀作(const int)*. 因此,這個標記序列將 type: 命名為指向 const 的指標int。
const T,其中T是型別名(可以是模板引數、型別別名或型別的名稱),是兩個標記的序列。組合時,它們命名為單一型別:const(任何T指定的型別)。
如果型別T恰好是 name int*,則型別T指定為 'pointer to int',如前所述。因此,const T指定 'const (pointer to int)' 請注意,指標是 const,而不是int被指向的物件。
型別別名和模板引數的替換不是通過復制和粘貼標記來完成的。它是通過將 typenames 規則應用于別名作為一個單元來完成的。無論T是什么,限定詞都const適用于T作為一個單位。
int * const是三個標記的序列。根據上述規則,const適用于其左側的任何內容(如果有)。所以const適用于*. 因此,這些標記將型別命名為: const 指向 的指標int。
T const是兩個標記的序列。當這樣組合時,它們命名為單一型別:const(任何型別T指定)。
這就是為什么T const和int * const行為相同,但const T并const int *沒有。
uj5u.com熱心網友回復:
(參考 C 14 標準)
您的f_non_template_*功能并不完全正確。
由于T是一個模板引數,它的行為就像是一個獨特的型別:
14.5.6.2 函式模板的部分排序
(3)生成轉換后的模板,為每個型別、非型別或模板模板引數(包括其模板引數包(14.5.3))分別合成一個唯一的型別、值或類模板 ,并在每次出現時替換它模板的函式型別中的那個引數。
因此,要正確測驗這一點,您的非模板函式需要像這樣定義:
using TT = int*;
void f_non_template_left(const TT t) {
/* ... */
}
void f_non_template_right(TT const t) {
/* ... */
}
Godbolt 示例
此時,您將獲得與模板化函式完全相同的行為。
為什么它這樣作業
在這種情況下T將被推匯出為int*,作為唯一型別將是復合型別:
3.9.2 化合物型別
(1)復合型別可以通過以下方式構造:
[...]
(1.3) — 指向 void 或給定型別的物件或函式(包括類的靜態成員)的指標
[...]
復合型別的 cv 規則如下:
3.9.3 簡歷限定詞
(1) 3.9.1和3.9.2【復合型別】中提到的型別是cv-unqualified型別。每個型別是 cv 未限定的完整或不完整物件型別或 void (3.9) 具有其型別的三個相應的 cv 限定版本:constqualified 版本、volatile 限定版本和 const-volatile 限定版本。
(2) 復合型別 (3.9.2) 不受復合型別的 cv 限定符(如果有)的 cv 限定。任何應用于陣列型別的 cv 限定符都會影響陣列元素型別,而不是陣列型別 (8.3.4)。
所以你T的模板函式中的cv 限定符在你的T兩種情況下都是指復合型別的頂級常量,所以
template<typename T> void f_const_left(const T t);
template<typename T> void f_const_right(T const t);
實際上是等價的。
唯一的例外是 ifT是陣列型別,在這種情況下, cv 限定符將應用于陣列的元素。
如果要指定指向值的常量性,可以這樣做:
//const value
template<class T>
void fn(const T* value);
// const pointer
template<class T>
void fn(T* const value);
// const value const pointer
template<class T>
void fn(const T* const value);
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/385116.html
