C++20 concepts提供五种强制模板参数具备特定成员的方法:一、用标准库预定义概念(如std::ranges::range);二、自定义concept检测嵌套类型(如typename T::value_type);三、检测成员函数签名(如t.size()->std::same_as);四、组合多个概念(如HasBeginEnd&&HasSizeMember&&HasSubscript);五、直接在函数模板后使用requires表达式。

如果您在编写C++函数模板时希望强制要求模板参数类型必须具备特定成员(如value_type、begin()、size()或可调用的operator()等),而非依赖运行时断言或编译期SFINAE硬编码,那么C++20引入的concepts提供了直接、可读、可诊断的约束机制。以下是实现该目标的多种可行方法:
一、使用标准库预定义概念进行基础约束
标准库提供了一系列常用概念(如std::regular、std::semiregular、std::equality_comparable、std::ranges::range等),可快速验证类型是否满足常见语义要求。例如,若要求模板参数支持迭代访问,则可直接约束为std::ranges::range,该概念已隐含要求类型具有嵌套的iterator、begin()和end()成员。
1、包含头文件<concepts></concepts>和<ranges></ranges>;
2、声明函数模板时,在模板参数列表后使用requires子句;
立即学习“C++免费学习笔记(深入)”;
3、将参数类型约束为std::ranges::range,例如:template<:ranges::range r> void process(R&& r);</:ranges::range>;
4、调用时传入std::vector<int></int>、std::array<double></double>等满足条件的类型可成功编译;
5、若传入int或无begin()成员的自定义类,则编译器将报错并明确指出std::ranges::range约束未满足。
二、定义自定义概念检测特定成员类型
当需检查某类型是否含有名为value_type的嵌套类型时,可使用concept配合typename T::value_type表达式构建“成员类型存在性”约束。该方式不依赖具体值,仅验证语法合法性。
1、使用template<typename t></typename>声明概念;
2、在concept定义体中使用requires块;
3、在requires块内写出合法表达式:typename T::value_type;
4、完整写法示例:template<typename t> concept HasValueType = requires { typename T::value_type; };</typename>;
5、后续函数模板即可使用该概念:template<hasvaluetype t> void foo(T t);</hasvaluetype>;
6、对std::vector<int></int>或自定义含using value_type = int;的类,约束通过;
7、对不含该嵌套类型的类,编译器将拒绝实例化,并高亮提示T does not satisfy HasValueType: typename T::value_type is not valid。
三、定义自定义概念检测特定成员函数签名
若需确保类型支持带无参size()且返回整型的成员函数,应使用requires表达式验证可调用性及返回类型。此方式不仅检查函数存在,还校验其调用结果是否可转换为std::size_t。
1、声明概念template<typename t> concept HasSizeMember = requires(T t) { { t.size() } -> std::same_as<:size_t>; };</:size_t></typename>;
2、注意requires(T t)引入假想对象用于表达式求值;
3、花括号内为待验证表达式,箭头后为期望的类型约束;
4、std::same_as<:size_t></:size_t>确保t.size()返回值类型严格匹配;
5、将该概念用于模板参数:template<hassizemember c> auto get_size(C const& c) { return c.size(); }</hassizemember>;
6、std::string、std::deque<char></char>等满足约束;
7、若某类型size()返回int,则因不满足std::same_as<:size_t></:size_t>而被排除,错误信息将highlight the exact type mismatch in diagnostic。
四、组合多个概念构建复合约束
实际开发中常需同时满足多项成员特性,例如:容器类型既要能迭代(有begin/end),又要能获取大小(size()),还需支持元素访问(operator[])。此时可使用逻辑运算符组合已有概念,形成更精确的接口契约。
1、先分别定义HasBeginEnd、HasSizeMember、HasSubscript三个独立概念;
2、使用&&连接多个概念:template<hasbeginend c></hasbeginend>;
3、也可用requires子句内联组合:template<typename c> requires (HasBeginEnd<c> && HasSizeMember<c> && HasSubscript<c>) void analyze(C const& c);</c></c></c></typename>;
4、每个子概念失败时,编译器将单独报告对应约束项;
5、最终错误诊断可精准定位到first failed requirement: HasSubscript
6、避免传统SFINAE中“所有重载均失效导致模糊错误”的问题。
五、使用requires表达式直接嵌入函数模板声明
对于一次性、轻量级约束,无需单独定义概念,可将requires表达式直接附加于函数模板声明之后。这种方式简洁,适用于局部逻辑,且约束逻辑与函数定义紧密耦合,便于维护。
1、在函数模板声明末尾添加requires关键字;
2、后接括号包围的布尔常量表达式或requires块;
3、例如:template<typename t> auto get_value(T&& t) requires requires(T&& x) { { x.value() } -> std::convertible_to<int>; } { return t.value(); }</int></typename>;
4、此处要求t.value()可被隐式转换为int,而非必须是int;
5、std::optional<int></int>若定义了value()且返回int&,则满足;
6、若返回long,则因long可隐式转int仍可能通过(取决于上下文);
7、若需严格类型一致,应改用std::same_asstd::convertible_to<int></int>。


















