C++20 概念讓樣板宣告能在其介面中陳述可由編譯器檢查的要求。只有在其相關限制滿足時,受限制的候選項才會參與多載解析。因此,限制可用暴露預期作業集合的宣告,取代許多 std::enable_if SFINAE 用法;但限制無法證明 API 文件所述的語意要求。
宣告限制 #
概念是命名空間範圍、類似變數的樣板,其定義是 bool 常數運算式:
#include <concepts>
template <class T>
concept Addable = requires(T value) {
{ value + value } -> std::convertible_to<T>;
};使用 requires 子句、受限制的樣板參數或縮寫函式樣板套用概念:
template <class T>
requires Addable<T>
T sum(T left, T right) {
return left + right;
}
std::integral auto increment(std::integral auto value) {
return value + 1;
}每個 std::integral auto 都會引入獨立的樣板參數。兩個引數必須具有相同推導型別時,請使用明確參數。尾端 requires 子句在限制接續宣告子之後時很有用,包括類別樣板的成員:
template <class T>
struct Buffer {
void compact() requires std::movable<T>;
};requires 子句不能套用至非樣板的自由函式。多個宣告位置的限制會以 && 組合;替換依賴某項先決條件的要求時,請將先決條件放在前面。
Requires 運算式與語意要求 #
requires 運算式會檢查要求而不評估其運算元。它支援簡單要求、型別要求、複合要求與巢狀要求:
#include <concepts>
#include <iterator>
template <class T>
concept SortableRange = requires(T range) {
typename T::value_type; // type
{ range.begin() } -> std::input_or_output_iterator; // compound
range.end(); // simple
requires std::movable<typename T::value_type>; // nested
};替換後無效的要求會使相依限制不滿足,而不是在呼叫點產生診斷。對所有可能替換都無效的要求則是不合法的,且不要求診斷。
滿足條件是語法性的。型別可能滿足相等運算式要求,卻違反 std::equality_comparable 預期的等價關係;型別可能讓 swap 編譯成功,卻違反其所述的複雜度或後置條件。請記錄並測試這些語意要求,因為編譯器無法建立它們。
從 enable_if 遷移
#
請遷移既有的選取原則,而不要僅因概念可用就擴大多載集合:
// Before C++20.
template <class T, std::enable_if_t<std::integral<T>, int> = 0>
T twice(T value) { return value + value; }
// C++20, with the same intended eligibility.
template <std::integral T>
T twice(T value) { return value + value; }限制會選取候選項的可行性;它們不會將已選取函式本體中的錯誤轉為替換失敗。取代 enable_if 時,請保留排除案例、轉換與排序測試,尤其是舊多載使用預設樣板參數或回傳型別 SFINAE 的情況。
排序與包含 #
當多個受限制多載都滿足時,語言會使用限制包含關係選取限制較多的候選項。包含關係是結構性的,依據正規化的原子限制,而不是任意的布林等價。請命名共用述詞並組合它們:
template <class T>
concept Range = requires(T value) { value.begin(); value.end(); };
template <class T>
concept SizedRange = Range<T> && requires(T value) { value.size(); };
template <Range T>
void inspect(const T&);
template <SizedRange T>
void inspect(const T&); // selected when both are satisfied
重複恰好具有相同意義的運算式,可能建立不同的原子限制,使多載產生歧義。具名概念會保留排序規則所需的原子限制識別。
轉送參照與客製化點 #
對 T 已推導的 T&&,左值引數會將 T 推導為左值參照型別。契約涉及底層物件時,請限制 std::remove_cvref_t<T>:
#include <ranges>
#include <type_traits>
template <class R>
requires std::ranges::range<std::remove_cvref_t<R>>
void consume(R&& range);只有在刻意需要參照限定時,才直接限制 R。這項區別可避免左值僅因概念是為物件型別撰寫,就遭到拒絕。
客製化點物件(CPO)通常是 inline constexpr 函式物件,其受限制的呼叫運算子實作定義的客製化協定。例如,std::ranges::begin 可以從陣列、適合的成員 begin,或透過引數相依查找找到的未限定 begin 取得迭代器。限制應在 CPO 邊界描述支援的協定,讓不支援的型別在該介面失敗,而不是在選取的客製化內部深處失敗。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)