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)

  1. 推導出的型別通常可透過 IDE 編輯器編譯器錯誤訊息Boost TypeIndex 函式庫檢視。
  2. 某些工具的結果既不實用也不準確,因此徹底理解 C++ 型別推導規則(Items 1-3)仍然不可或缺。

三個階段的檢視管道

 編輯期 (edit)          編譯期 (compile)           執行期 (runtime)
+----------------+    +---------------------+    +------------------------+
| IDE hover 提示 | -> | TD<decltype(x)> 技巧| -> | typeid / Boost.TypeIndex|
| 需程式碼大致可 |    | 錯誤訊息透露型別    |    | typeid: 不保證正確      |
| 編譯(內建前端) |    | (最準確、零依賴)    |    | TypeIndex: 準確可讀     |
+----------------+    +---------------------+    +------------------------+

編譯期技巧:未定義模板 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
變數命名成 xTypeyType 這種 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 輸出 PK6WidgetPK = 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 = intPKi = 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&
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 的型別推導規則為最終依據