乍一看這個標(biāo)題很玄乎,但是其實這只是涉及一個很簡單的CPP的模板推導(dǎo)的知識點。
筆者近期進行CPP開發(fā)工作時,在編譯時遇到了如下的模板類型的推斷錯誤:note: candidate template ignored: deduced conflicting types for parameter T (long long vs. long int)。通過一番梳理之后總結(jié)成文,希望對大家有所幫助。
1.非推斷語境
眾所周知,函數(shù)模板的使用是C++編譯期進行類型推導(dǎo)的過程。通過分析源代碼之中函數(shù)實參的類型,進一步推斷出調(diào)用的函數(shù)參數(shù)的類型,從而自動生成對應(yīng)的函數(shù),來達到精簡代碼邏輯的效果。
而所謂非推斷語境呢?則是模板的類型不參與模板實參推導(dǎo),取而代之地使用可在別處推導(dǎo)或顯式指定的模板實參。
單看上述文字可能很難理解,咱們直接看代碼就能明白了。
2.舉個栗子
我們先來看看下面的一段簡單的代碼:
template<typename T>
struct TestTemplate {
T t;
};
template<typename T>
T add(TestTemplate<T>& test, T val) {
return test.t + val;
}
int main() {
TestTemplate<long> test_template{100};
return add(test_template, 10);
}
在進行編譯的時候出現(xiàn)如下的報錯:
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'T' ('long int' and 'int')
通過gcc的編譯報錯我們可以看出,這里出現(xiàn)了錯誤的模板推斷問題。模板函數(shù)add在進行類型推斷時出現(xiàn)了沖突,在同一個函數(shù)中,模板類型T被同時推斷為long與int。
我們來分析一下模板推斷的流程。
- 首先,參數(shù)
test_template的類型為TestTempalate<long>, 它作為add函數(shù)的第一個參數(shù)傳入,此時T的類型被推導(dǎo)為了long。 - 接著,參數(shù)
val的類型為int, 它作為add函數(shù)的第二個參數(shù)傳入,而此時由于13為int類型,所以T被推導(dǎo)為int類型。
正是因為這樣,在add函數(shù)進行模板推導(dǎo)的過程之中,兩個參數(shù)test與val同時參與了模板類型的推導(dǎo),導(dǎo)致出現(xiàn)了上述的問題。
我們可以嘗試將add函數(shù)的調(diào)用改為如下:add(test_template, 10l)。此時val也作為參數(shù)T也被推導(dǎo)為long類型,則編譯不再報錯。
3. 利用非推斷語境解決問題
顯然,上面的代碼我們希望編譯器支持將int類型自動推導(dǎo)為long,而不要出現(xiàn)惱人的報錯。那我們就需要利用非推斷語境來解決問題了,讓val的類型不要參與到類型推導(dǎo)過程之中來,那么問題就解決了。
模板的非推斷語境出現(xiàn)比較復(fù)雜,有需要的可以參考cppreference部分的詳細解釋。我們將利用第一種,也是最常見的非推斷語境來解決上文提到的問題。
The nested-name-specifier (everything to the left of the scope resolution operator ::)
簡單來說就是::左側(cè)作用域的類型,不參與模板類型的推導(dǎo)。
所以上述代碼改為如下代碼,就可以規(guī)避原先的問題了。
template<typename T>
struct TestTemplate {
T t;
};
template<typename T> struct identity { typedef T type; };
template<typename T>
T add(TestTemplate<T>& test, typename identity<T>::type val) {
return test.t + val;
}
int main() {
TestTemplate<long> test_template{1000};
return add(test_template, 10);
}
這里我們新添加了類型identity, 并利用typename identity<T>::type規(guī)避了模板的類型推斷過程,從而讓val的類型推斷直接利用了test參數(shù)的類型推斷結(jié)果,所以此時val的類型為long,模板類型推斷也就不再出錯了。
正是因為非推斷語境在模板推斷中會被使用,所以C++20提供了新的trait:
std::type_identity與std::type_identity_t來幫助我們解決上述的問題。它們的實現(xiàn)與功能與上面展示的identity一致,都是利用模板的非推斷語境來規(guī)避類型推斷不同導(dǎo)致的編譯失敗問題。
4.小結(jié)
C++的一些模板推斷的問題常常讓人抓狂,很多時候gcc給出的一長串報錯很容易勸退萌新。本篇聊了聊筆者實際在開發(fā)中遇到的模板推斷問題出發(fā),一步步分析報錯,希望大家對解決編譯問題有耐心,并擅用搜索引擎,功力必不唐捐。(當(dāng)然,更新的C++標(biāo)準(zhǔn)也給我們解決問題的武器庫添磚加瓦,多多學(xué)習(xí)才是正道,日常一念:C++20好~~~)
希望大家能夠有所收獲,筆者水平有限。成文之處難免有理解謬誤之處,歡迎大家多多討論,指教。