c++ constraints與concepts使用筆記
- 1. 模板參數缺乏約束的問題
- 2. Concepts 基本概念
- 3. Concept 的定義與使用
- 4. requires 表達式詳解
- 5. requires 從句 vs requires 表達式
- 完整示例:約束矩陣運算
1. 模板參數缺乏約束的問題
問題分析:
- 傳統模板參數沒有語法層面的約束,需要程序員自行通過代碼邏輯理解參數要求
- 編譯器錯誤信息不友好,尤其在傳遞非法參數時(如
vector<int&>
) - 類型檢查發生在模板實例化時,而非聲明時
示例:
template<typename T>
class Container {T data[10];
public:void copy_from(const Container& other) {std::copy(std::begin(other.data), std::end(other.data), data);}
};struct NonCopyable {NonCopyable(const NonCopyable&) = delete;
};Container<NonCopyable> c; // 編譯錯誤出現在實例化時的copy操作,而非類定義處
2. Concepts 基本概念
核心特性:
- C++20 引入的編譯期謂詞機制
- 通過
requires
子句顯式約束模板參數 - 提升代碼可讀性和編譯器錯誤信息質量
示例:
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;template<Arithmetic T> // 約束T必須是算術類型
T add(T a, T b) { return a + b; }add(3, 5); // OK
add("a", "b"); // 明確的編譯錯誤:不滿足Arithmetic約束
3. Concept 的定義與使用
(1) 單參數 Concept
template<typename T>
concept HasSize = requires(T v) {{ v.size() } -> std::convertible_to<size_t>;
};template<HasSize T>
void print_size(T obj) {std::cout << obj.size() << "\n";
}std::vector v{1,2,3};
print_size(v); // OK
print_size(42); // 錯誤:int沒有size()方法
(2) 多參數 Concept
template<typename T, typename U>
concept SameAs = std::is_same_v<T, U>;template<typename T>
concept AddableToInt = requires(T a) {{ a + 1 } -> SameAs<int>; // 使用兩參數Concept
};AddableToInt auto result = 'A' + 1; // OK,char + int 返回int
4. requires 表達式詳解
(1) 簡單表達式 表明可以接收的操作
template<typename T>
concept Incrementable = requires(T v) {++v; // 檢查前置++v++; // 檢查后置++
};static_assert(Incrementable<int>); // 通過
static_assert(Incrementable<std::string>); // 失敗
(2) 類型表達式 表明是一個有效的類型
template<typename T>
concept HasValueType = requires {typename T::value_type; // 檢查嵌套類型是否存在
};static_assert(HasValueType<std::vector<int>>); // 通過
static_assert(HasValueType<int>); // 失敗
(3) 復合表達式 表明操作的有效性,以及操作返回類型的特性
template<typename T>
concept StringConvertible = requires(T obj) {{ std::to_string(obj) } -> std::same_as<std::string>;
};static_assert(StringConvertible<int>); // 通過
static_assert(StringConvertible<void*>); // 失敗
(4) 嵌套表達式 包含其它的限定表達式
template<typename T>
concept CompleteType = requires {sizeof(T); // 檢查類型完整性requires !std::is_void_v<T>; // 組合多個條件
};static_assert(CompleteType<int>); // 通過
static_assert(CompleteType<void>); // 失敗
5. requires 從句 vs requires 表達式
關鍵區別:
// requires 從句(用于模板參數約束)
template<typename T>
requires std::is_integral_v<T> // ← 這是requires從句
void process(T value) { /*...*/ }// requires 表達式(用于定義Concept的約束條件)
template<typename T>
concept Streamable = requires(T v, std::ostream& os) {{ os << v } -> std::same_as<std::ostream&>;
};
完整示例:約束矩陣運算
template<typename T>
concept Numeric = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;template<typename M>
concept Matrix = requires(const M& mat, size_t i, size_t j) {{ mat.rows() } -> std::convertible_to<size_t>;{ mat.cols() } -> std::convertible_to<size_t>;{ mat(i,j) } -> Numeric;typename M::value_type;requires Numeric<typename M::value_type>;
};template<Matrix A, Matrix B>
auto multiply(const A& a, const B& b) {using T = std::common_type_t<typename A::value_type, typename B::value_type>;std::vector<std::vector<T>> result(a.rows(), std::vector<T>(b.cols()));// ... 矩陣乘法實現return result;
}
以下是對上述代碼詳細分步解釋:
- Numeric概念定義
template<typename T>
concept Numeric = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;
- 作用:定義數值類型約束,排除布爾類型。
- 機制:
std::is_arithmetic_v<T>
檢查T
是否為算術類型(整型/浮點型,包括bool
)。!std::is_same_v<T, bool>
排除bool
類型。
- 合法類型示例:
int
,double
,float
。 - 排除類型示例:
bool
,std::string
。
- Matrix概念定義
template<typename M>
concept Matrix = requires(const M& mat, size_t i, size_t j) {{ mat.rows() } -> std::convertible_to<size_t>;{ mat.cols() } -> std::convertible_to<size_t>;{ mat(i,j) } -> Numeric;typename M::value_type;requires Numeric<typename M::value_type>;
};
- 作用:定義矩陣類型的編譯期接口約束。
- 要求:
- 維度接口:必須提供返回
size_t
的rows()
和cols()
方法。 - 元素訪問:支持
operator(i,j)
且返回值滿足Numeric
。 - 元素類型:必須通過
value_type
公開元素類型,且該類型滿足Numeric
。
- 維度接口:必須提供返回
- 示例合規類型:包含上述方法和嵌套類型的自定義矩陣類。
- 矩陣乘法函數模板
template<Matrix A, Matrix B>
auto multiply(const A& a, const B& b) {using T = std::common_type_t<typename A::value_type, typename B::value_type>;std::vector<std::vector<T>> result(a.rows(), std::vector<T>(b.cols()));// 矩陣乘法實現(待填充)return result;
}
- 模板約束:
A
和B
必須滿足Matrix
概念。 - 實現步驟:
- 公共類型計算:
std::common_type_t
推導兩種元素類型的公共可兼容類型(如int+double→double
)。 - 結果容器初始化:創建
a.rows()×b.cols()
的二維向量,元素默認初始化為T()
。 - 乘法邏輯(需補充):典型的三重循環遍歷行、列,進行點積運算。
- 公共類型計算:
優勢:
- 顯式約束矩陣類型必須具有
rows()
,cols()
方法 - 元素訪問操作
operator()
必須返回數值類型 - 矩陣元素類型必須滿足
Numeric
約束 - 編譯錯誤會明確指出具體違反的約束條件
通過合理使用 Concepts 和 requires 表達式,可以顯著提升模板代碼的可維護性和錯誤信息的可讀性,同時增強接口的自我描述能力。