C++ 從基礎到精通 — 第 16 講
使用 Concepts 與 requires 表達模板需求
本部落格文章以教學為目的撰寫。 內容可能有誤,請搭配目標編譯器與標準函式庫文件確認。
模板能把同一規則套用到多種型別,但不支援的型別常會在實作深處產生冗長錯誤。 C++20 的 Concepts 與 requires 可把需求寫在模板的公開宣告中。
沒有限制的模板
template <typename T>
int GetHealth(const T& value)
{
return value.Health();
}
此函式只適用於能呼叫 Health() 的型別,但簽章沒有表達這項條件。編譯器必須具現化函式本體後才會發現錯誤。
用 Concept 命名需求
#include <concepts>
template <typename T>
concept Damageable = requires(T value, int amount)
{
{ value.Health() } -> std::convertible_to<int>;
{ value.ApplyDamage(amount) } -> std::same_as<void>;
};
template <Damageable T>
void Attack(T& target, int damage)
{
target.ApplyDamage(damage);
}
requires expression 不會執行程式碼,只檢查語法與結果型別是否有效。現在呼叫前就能看出 Attack 的需求,錯誤也會更接近呼叫位置。
組合型別條件
template <typename T>
concept SmallId = std::integral<T> && (sizeof(T) <= 4);
template <SmallId T>
void WriteId(T id);
Concept 可用邏輯運算組合 type trait 與 requires。限制條件也會參與多載排序。
void Print(std::integral auto value);
void Print(std::floating_point auto value);
帶 Concept 的 auto 是函式模板的簡寫。當整數與浮點數語意不同時,分離多載通常比在一個函式內塞入大型 if constexpr 更清楚。
語法有效不代表語意正確
a < b 能編譯,不代表比較一定形成一致順序;能複製也不保證兩份資料在語意上彼此獨立。
標準 Concepts 除了語法也記錄語意需求。自訂 Concept 應使用有意義的名稱,重要規則仍要透過測試驗證。
不要加入超過實作需要的限制。應表達最小且真實的契約,避免在沒有提升正確性的情況下拒絕可用型別。