C++20模板函数requires子句位置选择及重载解析差异
C++20 requires子句位置与重载解析差异
1. 何时将requires子句置于函数签名中而非模板参数列表之后?
需要引用函数参数时:模板参数列表后的requires子句声明在函数参数之前,无法访问函数参数变量或其推导类型。如果约束需要基于函数参数的具体属性(比如引用限定、const修饰、从参数推导的类型等),必须将requires放在函数签名之后。例如:
template <typename T> bool process(T&& x) requires std::is_integral_v<std::remove_reference_t<decltype(x)>> { /* ... */ }这里通过
decltype(x)获取参数类型,这在模板参数后的requires中无法实现,因为此时x还未被声明。使用缩写函数模板时:缩写模板(如用
auto作为参数类型)没有显式的模板参数列表,只能将requires子句放在函数签名之后。例如:bool calculate(auto x) requires std::is_floating_point_v<decltype(x)> { /* ... */ }提升代码可读性:当约束直接关联到函数的参数行为,而非模板本身的通用约束时,将requires放在函数签名后更直观,能让读者快速理解约束与函数参数的关联关系。
2. foo与bar这类函数的重载解析差异
在示例代码中,foo和bar的调用结果看似一致,但约束的处理逻辑和适用场景存在本质差异:
模板级约束(foo的写法)
第一个foo模板的requires子句属于模板参数列表的一部分,约束直接作用于模板参数T:
- 重载解析时,编译器首先检查模板的约束是否满足:若
T是整数类型,第一个foo模板成为可行候选;否则该模板被SFINAE规则排除,只能匹配第二个无约束的foo。 - 由于带约束的模板比无约束模板更“特化”(C++20偏序规则),当约束满足时,第一个foo会被优先选中。
- 局限性:只能基于模板参数本身的静态属性编写约束,无法引用函数参数。
函数级后置约束(bar的写法)
第一个bar模板的requires子句属于函数声明的一部分:
- 重载解析时,编译器先将两个bar模板都视为候选,再逐一检查约束是否满足:若
T是整数类型,第一个bar的约束满足,成为可行候选;第二个无约束的bar始终可行。 - 由于两个模板的签名完全相同,仅约束不同,根据规则,约束更严格的候选(第一个bar)会被优先选中。
- 灵活性:可以引用函数参数的名称或推导类型,支持更复杂的约束逻辑(比如基于参数的引用类型、const修饰等)。
核心差异总结:模板级约束(foo)仅能约束模板参数,适合定义模板的通用前置条件;函数级后置约束(bar)可关联函数参数,适合定义与函数调用直接相关的约束,灵活性更高。
内容的提问来源于stack exchange,提问作者nebuch
相关产品推荐
相关产品推荐

