requires表达式需显式声明参数并用复合要求检查成员函数签名,配合std::declval验证const/引用场景,返回类型优先用std::convertible_to而非std::same_as,并分步检测嵌套类型存在性与可访问性,否则约束易失效。

直接说结论:用 requires 表达式写具体操作约束,比仅依赖 std::integral 这类标准概念更精细;但必须配合 typename 检测、返回类型约束和语义检查三者,否则容易漏掉引用、cv 限定或隐式转换问题。
怎么用 requires 表达式检查成员函数签名
标准概念(如 std::ranges::range)只管“有没有”,不保证“能不能用”。比如你要求类型有 size(),但没说它是否 noexcept、是否返回 std::size_t、是否对 const 对象可用——这些都得靠 requires 显式写出来:
- 写法必须带参数声明和表达式块:
requires(T t) { { t.size() } -> std::convertible_to<:size_t>; }</:size_t> - 如果还要支持
const版本,得额外加一行:{ std::declval<const t>().size() } -> std::convertible_to<:size_t>;</:size_t></const> - 漏掉
std::declval直接写const T().size()会触发构造,可能失败且不反映真实调用场景 - 返回类型用
std::convertible_to比std::same_as更宽松合理;std::same_as要求完全一致,但很多容器的size()返回size_type,不是裸std::size_t
为什么 typename T::value_type 单独写在 requires 里不够
只写 typename T::value_type 只能确认嵌套类型存在,但无法防止它是个私有类型、或被 private 继承屏蔽。真正安全的检测要分两步:
- 先用
requires { typename T::value_type; }确保语法合法 - 再加一条表达式验证可访问性:
{ std::declval<t>().value_type{} } -> std::same_as<typename t::value_type>;</typename></t>(注意这里用的是对象访问,不是静态类型) - 若类型未定义默认构造,第二条会失败——这时应改用
std::is_default_constructible_v<typename t::value_type></typename>做特征检测,而非硬调用 - 常见坑:把
typename T::value_type当成值来用(比如写t.value_type),实际它只是类型名,不能出现在表达式左边
std::same_as 和 std::convertible_to 到底该选哪个
二者语义差异极大,选错会导致约束过严或过松:
立即学习“C++免费学习笔记(深入)”;
-
std::same_as<a b></a>是严格等价:要求A和B是同一类型,连引用/const 都不能差。比如int和const int&不满足same_as -
std::convertible_to<a b></a>只要求A能隐式转成B,更贴近真实使用场景。例如std::vector<int>::size()</int>返回std::vector<int>::size_type</int>,它通常可转成std::size_t,但未必是同一类型 - 在 requires 表达式中,返回类型约束几乎都该用
convertible_to;只有当你明确禁止任何转换(比如序列化接口要求字节布局完全一致),才考虑same_as - 误用
same_as是导致“明明能用却编译不过”的最常见原因,尤其在跨平台或自定义分配器场景下
模板参数加 const 或引用后,概念还成立吗
概念检查的是类型本身,不是模板实参的修饰形式。这意味着:
-
template<:integral t> void f(T)</:integral>允许传入int、const int、int&,因为std::integral检查的是去掉引用和 cv 限定后的int - 但如果你写的是
template<addable t> void f(const T&)</addable>,那Addable检查的仍是T,不是const T&;所以Addable定义里必须覆盖const T&场景,否则调用时可能失败 - 最稳妥的做法是在 concept 定义里显式测试不同 cv/ref 组合,比如:
requires(const T& a, const T& b) { { a + b } -> std::convertible_to<t>; }</t> - 别指望编译器自动“推导修饰”——concept 不做类型归一化,它只看你写的表达式能不能成立
精细化约束的关键不在堆砌条件,而在想清楚:这个模板真正会在哪些上下文中被调用?用户传进来的到底是值、const& 还是右值?每种情况下的操作是否都合法?漏掉任意一种,约束就只是看起来漂亮而已。


















