为何C++20 requires表达式有时会允许无效表达式?
C++20 requires表达式与constexpr if分支编译异常分析
问题背景
尝试通过C++20 requires 表达式结合constexpr if实现哈希计算策略的自动分发:
- 若类型支持与
Archive的&操作(即能被序列化),则用Archive的键作为哈希值 - 若类型提供
GetHashCode()方法,则调用该方法 - 否则返回默认值
但针对类型A(仅提供GetHashCode()),代码触发编译错误:A类没有serialize成员函数,而错误来自第一个分支的ar & t调用,尽管requires(Archive ar) { ar & t;}判定为真。
核心原因
问题出在requires表达式的检查逻辑与模板函数实例化的差异:
requires表达式的检查范围:当检查ar & t时,编译器仅做模板实参推断,判断是否存在匹配的函数重载,不会实例化模板函数的函数体。- 对于
operator&模板:template <typename T> void operator & (Archive & ar, T & t),编译器会将T推断为const A(因为t是const A&),此时函数签名void operator&(Archive&, const A&)是合法匹配,因此requires表达式返回true。
- 对于
- 分支代码的实例化:进入
constexpr if分支后,编译器需要实例化operator&<const A>的函数体,此时才发现A类没有serialize方法,触发编译错误。
解决方案
有两种可行的修正方式:
方式一:给operator&添加约束,确保仅当类型支持serialize时才存在该重载
修改operator&的定义,通过requires约束限定只有具备serialize方法的类型才能匹配:
template <typename T> requires requires(T& t, Archive& ar) { t.serialize(ar, 0); } void operator & (Archive & ar, T & t){ t.serialize(ar, 0); }
此时,requires(Archive ar) { ar & t;}会正确识别A类无法匹配operator&,从而跳过第一个分支。
方式二:直接在requires表达式中检查serialize能力
修改foo中的第一个requires条件,直接检查类型是否支持serialize调用,而非间接检查operator&:
template <typename T> std::string foo(T const & t) { if constexpr ( requires(Archive ar) { t.serialize(ar, 0); }){ Archive ar; ar & t; return ar.GetKey(); }else if constexpr( requires { t.GetHashCode(); }){ return t.GetHashCode(); }else return "NO HASH"; }
这种方式更直接,避免了通过operator&间接检查带来的逻辑偏差。
修正后代码验证
两种方式都能让foo(A{})正确进入第二个分支,返回"BHASH";foo(B{})进入第一个分支,返回"GetArchiveKey",编译运行正常。
内容的提问来源于stack exchange,提问作者bradgonesurfing
相关产品推荐
相关产品推荐

