编译器在ADL与约束表达式重载决议上的分歧及程序正确性判定
C++模板重载与ADL的编译器行为争议
代码示例
struct A{}; constexpr bool p(auto) { return false; } constexpr bool f(auto v) { return p(v); } constexpr bool g() { return f(A()); } constexpr bool p(auto) requires true { return true; } static_assert( f(A{}) );
现象描述
这段代码中,模板函数f调用重载的模板函数p,其中第二个p的重载声明在f之后。按依赖参数查找(ADL)的预期,该后续重载应在f<A>的实例化点被纳入候选。但实际编译结果不一致:
- MSVC、GCC
-O0:static_assert通过,说明选择了返回true的p重载 - Clang、GCC
-O1及更高优化级:static_assert失败,说明选择了返回false的p重载
问题解答
程序是否存在未定义行为?
不存在未定义行为,但程序属于格式错误——重载决议时出现歧义,没有唯一最优的候选函数。
哪个编译器行为正确?
Clang以及开启优化后的GCC行为正确。
原因分析
- 依赖名称的查找规则:模板
f中的p(v)是依赖函数调用(v是模板参数,p的查找依赖于v的类型),因此候选函数集由两部分组成:- 模板定义点的普通查找:找到第一个无约束的
p重载(返回false) - 实例化点的ADL查找:在
static_assert前已声明的第二个带约束requires true的p重载(返回true)被纳入候选
- 模板定义点的普通查找:找到第一个无约束的
- 重载决议的歧义:两个
p模板实例化后的函数签名完全相同,且约束等价(无约束等价于requires true),模板偏序也无法区分优先级。此时重载决议没有唯一最优候选,程序不符合标准要求。 - 编译器行为差异的原因:
- MSVC和GCC
-O0可能未严格执行重载决议规则,错误地优先选择了后续声明的p重载 - Clang和GCC
-O1及以上则严格遵循标准,判定重载决议存在歧义,或在处理时选择了定义点找到的p重载,最终导致static_assert失败
- MSVC和GCC
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

