含C++17 constexpr if的模板结构体在MSVC编译失败的原因
为什么这段带
if constexpr的模板代码在MSVC编译失败,却能通过Clang/GCC? 问题重现
先看这段代码在不同编译器下的表现:
template <typename T> struct foo { constexpr auto operator()() const { if constexpr (constexpr auto test = true; test) { return true; } else { return false; } } }; int main() { foo<int> bar{}; bar(); return 0; }
- MSVC:编译失败,抛出错误
C2065“'test': undeclared identifier” - Clang:无任何警告,顺利编译通过
- GCC(开启
-Wall):提示警告warning: variable 'test' set but not used [-Wunused-but-set-variable]
已知能让代码在MSVC上编译通过的方法有:
- 移除
foo的模板特性,将其改为普通结构体 - 移除
constexpr修饰(要么去掉if constexpr的constexpr,要么去掉变量test的constexpr) - 将
test的定义移到if constexpr外部:constexpr auto test = true; if constexpr (test)
原因剖析
本质是MSVC对C++标准中**if constexpr初始化语句的作用域规则**实现存在偏差。
根据C++标准,if constexpr (初始化语句; 条件)这种形式里,初始化语句声明的变量,其作用域应该覆盖整个if-else结构。也就是说,test变量在else分支里也是可见的——不过在这个例子里,if constexpr的条件是编译期常量true,else分支会被编译器完全丢弃,理论上根本不会执行到else里的代码,也不会用到test。
但MSVC在这里犯了个错误:它错误地认为test的作用域只限于if分支内部。当它扫描到else分支时,就会判定test是未声明的标识符,从而抛出错误。而Clang和GCC则正确遵循了标准:它们既认可test的作用域覆盖整个if-else,又因为else分支被编译期丢弃,所以不会触发任何未声明的错误(GCC的警告只是提示变量被赋值但没实际使用,也侧面说明它认为test是合法存在的)。
再解释下为什么那些解决方法有效:
- 移除模板:当
foo不是模板时,MSVC在编译阶段就能确定if constexpr的条件永远为true,直接把else分支丢弃,也就不会去检查test在else里的存在性了。 - 移除constexpr:如果换成普通
if语句,或者变量test不是constexpr,MSVC对初始化语句的作用域处理就会和标准一致,不会出现误判。 - 把test移到外部:这样
test的作用域明确覆盖整个if-else结构,MSVC能清晰找到这个变量,自然不会报错。
内容的提问来源于stack exchange,提问作者Wes Toleman
相关产品推荐
相关产品推荐

