未求值模板操作数是否需合法定义?主流C++编译器行为分歧解析
C++编译器对未求值操作数中非法表达式的行为分歧
以下代码中,4个static_assert的有效性在主流C++编译器中判定结果差异极大:
struct a { template<typename T = void> constexpr a() { (T&&)0; } }; struct b : a {}; template<typename T = void> constexpr int f() { return (T&&)0; } template<typename T = void> constexpr int v = (T&&)0; static_assert(sizeof(a{})); // #1 - clang 通过, gcc 报错, msvc 通过 static_assert(sizeof(b{})); // #2 - clang 报错, gcc 通过, msvc 通过 static_assert(sizeof(f())); // #3 - clang 通过, gcc 报错, msvc 通过 static_assert(sizeof(v<>)); // #4 - clang 通过, gcc 通过, msvc 报错
显然,T=void时的(T&&)0是非法表达式——C++不允许对void类型创建引用,但该表达式从未被实际求值,只是作为sizeof(未求值操作数)的一部分存在。为何各编译器的判定会出现如此分歧?
各编译器错误信息
Clang
<source>:3:23: error: cannot form a reference to 'void' 3 | constexpr a() { (T&&)0; } | ^ <source>:14:24: note: in instantiation of function template specialization 'a::a<void>' requested here 14 | static_assert(sizeof(b{})); | ^
GCC
<source>: In instantiation of 'constexpr a::a() [with T = void]': <source>:13:24: required from here 13 | static_assert(sizeof(a{})); | ^ <source>:3:21: error: forming reference to void 3 | constexpr a() { (T&&)0; } | ^~~~~~ <source>: In instantiation of 'constexpr int f() [with T = void]': <source>:15:23: required from here 15 | static_assert(sizeof(f())); | ~~^~~ <source>:8:28: error: forming reference to void 8 | constexpr int f() { return (T&&)0; } | ^~~~~~
MSVC
<source>(11): error C7683: you cannot create a reference to 'void' <source>(11): note: the template instantiation context (the oldest one first) is <source>(16): note: see reference to variable template 'const int v<void>' being compiled
分歧根源:模板实例化与未求值上下文的规则边界
问题核心在于编译器对模板实例化触发条件和未求值上下文豁免范围的标准解读差异:
构造函数场景(#1、#2)
- GCC认为,在
sizeof(a{})这类未求值上下文中,构造函数作为constexpr函数,其定义体必须被实例化以验证constexpr合法性,因此触发了(void&&)0的非法检查; - Clang和MSVC则认为未求值上下文不需要实例化构造函数体,但Clang在处理
b{}时,因继承构造的规则触发了模板构造函数的完整实例化,导致报错。
- GCC认为,在
函数模板场景(#3)
- GCC认为,即使
f()的返回类型明确为int,sizeof(f())仍需实例化函数体以确认返回值的有效性; - Clang和MSVC则判定,未求值上下文仅需确定函数的返回类型,无需实例化函数体。
- GCC认为,即使
变量模板场景(#4)
- MSVC认为,变量模板的初始化表达式属于必须验证的部分,哪怕在未求值上下文中也需要实例化并检查合法性;
- Clang和GCC则认为未求值上下文无需实例化变量模板的初始化表达式。
这些差异源于C++标准对“未求值上下文中模板实例化程度”的规则表述不够明确,导致各编译器的实现逻辑出现分歧。
内容的提问来源于stack exchange,提问作者303
相关产品推荐
相关产品推荐

