显式默认的==运算符重声明为何仅在前置static_assert时有效?
先看这段代码:
struct A { friend constexpr bool operator ==(const A &, const A &) noexcept = default; }; static_assert( A{} == A{} ); //移除此行程序会失效 constexpr bool operator ==(const A &, const A &) noexcept; static_assert( (&operator==)( A{}, A{} ) );
现象
保留static_assert( A{} == A{} )时,主流编译器(GCC、Clang、MSVC)均能正常编译;但移除该行后,编译器会报错:
error: 'constexpr bool operator==(const A&, const A&)' used before its definition
note: undefined function 'operator==' cannot be used in a constant
note: failure was caused by call of undefined function or one not declared 'constexpr'
原因解析
1. 类内友元函数的默认可见性
在类内部声明的友元函数(哪怕是显式默认的operator==),默认仅在类的作用域内可见,外部作用域无法直接通过普通名称查找到它——只有两种情况能让它被外部“看到”:要么通过依赖于参数的查找(ADL),要么外部有对应的函数声明。
2. 第一个static_assert的核心作用
static_assert( A{} == A{} )触发了ADL:因为操作数是A类型,编译器会自动在A的作用域内查找operator==,此时找到了类内声明的默认友元运算符。同时,因为这个运算符是= default的,编译器会立即为它生成定义(对于空结构体A,默认==的逻辑就是返回true)。
这一步不仅完成了断言,更关键的是促使编译器生成了该运算符的定义,并且通过ADL的查找过程,让编译器确认了这个函数的存在。
3. 外部重声明的作用
后续的constexpr bool operator ==(const A &, const A &) noexcept;是外部作用域的声明,它和类内的友元运算符是同一个函数(签名完全匹配)。此时因为之前已经通过ADL触发了函数定义的生成,这个外部声明相当于把已存在的函数“暴露”到外部作用域,让后续取函数指针&operator==的操作能直接找到它,并且因为函数已有constexpr定义,能在常量表达式中调用。
4. 移除第一个static_assert后的问题
如果去掉第一个断言,当编译器遇到外部的重声明时,只会看到这个空的声明,而类内的友元运算符因为从未被ADL触发查找,编译器不会去类的作用域中寻找它,自然也不会生成它的定义。此时取&operator==并尝试在常量表达式中调用,编译器只能找到外部的声明,找不到对应的定义,因此报错“使用未定义的函数”,且无法满足常量表达式的要求。
内容的提问来源于stack exchange,提问作者Fedor

