Item 4:學會檢視推導出的型別 (Know How to View Deduced Types)
Overview Table
| 開發階段 | 工具 | 做法 | 可靠度 |
|---|---|---|---|
| 編輯期 | IDE 編輯器 | 游標懸停(hover)顯示型別 | 簡單型別 OK;複雜型別常難以閱讀 |
| 編譯期 | 編譯器錯誤訊息 | 未定義的類別模板 TD(Type Displayer)觸發錯誤 |
準確,錯誤訊息必含完整型別 |
| 執行期 | typeid + std::type_info::name |
印出型別名稱字串 | 不可靠:規格強制去除 reference 與頂層 const/volatile |
| 執行期 | Boost.TypeIndex (type_id_with_cvr) |
pretty_name() 印出人類可讀型別 |
準確,保留 cv 與 reference 修飾 |
核心結論(Things to Remember)
- 推導出的型別通常可透過 IDE 編輯器、編譯器錯誤訊息與 Boost TypeIndex 函式庫檢視。
- 某些工具的結果既不實用也不準確,因此徹底理解 C++ 型別推導規則(Items 1-3)仍然不可或缺。
三個階段的檢視管道
編輯期 (edit) 編譯期 (compile) 執行期 (runtime)
+----------------+ +---------------------+ +------------------------+
| IDE hover 提示 | -> | TD<decltype(x)> 技巧| -> | typeid / Boost.TypeIndex|
| 需程式碼大致可 | | 錯誤訊息透露型別 | | typeid: 不保證正確 |
| 編譯(內建前端) | | (最準確、零依賴) | | TypeIndex: 準確可讀 |
+----------------+ +---------------------+ +------------------------+
- IDE:內部其實跑著一個 C++ 編譯器前端,因此程式碼必須處於「大致可編譯」狀態才有提示。
- 對
int這種簡單型別很好用;一旦牽涉 STL 容器內部型別,IDE 可能吐出一長串std::_Simple_types<...>::value_type *之類的雜訊。
編譯期技巧:未定義模板 TD
宣告一個只有宣告、沒有定義的類別模板,再用想檢視的型別去實體化,編譯器就會在錯誤訊息中「說出」該型別:
template<typename T> // 只宣告不定義
class TD; // TD = "Type Displayer"(型別顯示器)
const int theAnswer = 42;
auto x = theAnswer; // 推導為 int
auto y = &theAnswer; // 推導為 const int*
TD<decltype(x)> xType; // 錯誤訊息會包含 x 的型別
TD<decltype(y)> yType; // 錯誤訊息會包含 y 的型別
典型的錯誤輸出(不同編譯器措辭不同、資訊相同):
error: aggregate 'TD<int> xType' has incomplete type and cannot be defined
error: aggregate 'TD<const int *> yType' has incomplete type and cannot be defined
變數命名成
xType、yType 這種 variableNameType 形式,可讓錯誤訊息更容易搜尋定位。執行期:typeid 為何不可靠
template<typename T>
void f(const T& param) {
std::cout << "T = " << typeid(T).name() << '\n'; // 顯示 T
std::cout << "param = " << typeid(param).name() << '\n'; // 顯示 param
}
std::vector<Widget> createVec(); // 工廠函式
const auto vw = createVec();
if (!vw.empty()) f(&vw[0]); // 呼叫 f,實參型別為 const Widget*
GNU/Clang 輸出 PK6Widget(PK = pointer to const,6 = 類別名稱長度),MSVC 輸出 class Widget const *——T 與 param 竟然顯示成同一型別,這顯然有問題:param 宣告為 const T&,真正型別是 const Widget* const&。
| 對象 | 真正型別 | typeid(...).name() 回報 |
正確? |
|---|---|---|---|
T |
const Widget* |
const Widget*(mangled:PK6Widget) |
正確 |
param |
const Widget* const& |
const Widget* |
錯誤 |
std::type_info::name 依規格必須把型別視為「以 by-value 方式傳入模板函式」處理:先去除 reference-ness,再去除頂層 const/volatile(即 Item 1 的 Case 3 規則)。因此對 reference 或 cv 修飾型別的回報必然失真,不只是實作品質問題。 GNU/Clang 回傳的是 mangled name(如
i = int、PKi = const int*),需用 c++filt 工具解碼;MSVC 則直接輸出可讀名稱。此外 IDE 顯示的複雜型別同樣「不保證有用」,不可盡信。Boost.TypeIndex:準確的執行期輸出
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param) {
using boost::typeindex::type_id_with_cvr; // with_cvr = 保留 const/volatile/reference
std::cout << "T = "
<< type_id_with_cvr<T>().pretty_name() << '\n';
std::cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name() << '\n';
}
同一個 f(&vw[0]) 呼叫,GNU/Clang 下的輸出(準確):
T = Widget const*
param = Widget const* const&
type_id_with_cvr<T>()不會移除 const、volatile、reference 修飾(名稱中的with_cvr即此意)。- 回傳
boost::typeindex::type_index物件,其pretty_name()產生人類可讀的std::string。
Boost.TypeIndex 不屬於標準 C++(但跨平台、開源、授權友善,可攜性近乎標準函式庫)。且無論工具多準確,它們終究只是輔助——最終仍須靠 Items 1-3 的推導規則親自驗證理解。
Exam/Test Patterns
| 情境關鍵字 | 答案 |
|---|---|
| 「不加任何函式庫,最準確看推導型別的方法」 | 未定義類別模板 TD<decltype(x)> xType;,讀編譯錯誤訊息 |
| 「IDE hover 沒有提示」 | 程式碼必須大致可編譯,IDE 內建編譯器前端才能推導 |
「typeid(param).name() 少了 const 和 &」 |
規格強制以 by-value 規則處理:先去 reference,再去頂層 cv |
「GNU/Clang 印出 PKi」 |
mangled name:P=pointer、K=const,即 const int*;用 c++filt 解碼 |
| 「執行期要準確含 cv/reference 的型別字串」 | boost::typeindex::type_id_with_cvr<T>().pretty_name() |
| 「工具回報互相矛盾怎麼辦」 | 工具僅是輔助,以 Items 1-3 的型別推導規則為最終依據 |
Related Notes
- 02-Deducing-Types/01-Template-Type-Deduction —
typeid失真的根源正是 Item 1 的 by-value 推導規則(Case 3) - 02-Deducing-Types/02-Auto-Type-Deduction — 檢視
auto變數實際推導結果的主要應用場景 - 02-Deducing-Types/03-Decltype —
TD<decltype(x)>技巧依賴 decltype 取得精確宣告型別;decltype(auto)的意外結果也靠本 Item 工具驗證 - 03-Auto/01-Prefer-Auto-To-Explicit-Types — 廣泛使用 auto 後,更需要檢視推導型別的手段
- 03-Auto/02-Explicitly-Typed-Initializer-Idiom — 懷疑 auto 推導出 proxy type(如
std::vector<bool>::reference)時,先用本 Item 工具確認