C++霧中風(fēng)景17:模板的非推斷語境與std::type_identity

乍一看這個標(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被同時推斷為longint

我們來分析一下模板推斷的流程。

  • 首先,參數(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ù)testval同時參與了模板類型的推導(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_identitystd::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好~~~

希望大家能夠有所收獲,筆者水平有限。成文之處難免有理解謬誤之處,歡迎大家多多討論,指教。

5.參考資料

CppReference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容