为何if constexpr未选中块中恒为语义错误的依赖表达式不报错?
关于if constexpr与依赖/非依赖表达式的疑问
在if constexpr、依赖表达式(dependent expression)与非依赖表达式(non-dependent expression)的区分上,尤其是结合static_assert的场景,存在不少困惑:
- 在CWG2518修复前,直接写
static_assert(false)会在模板定义阶段就报错,必须借助技巧让断言条件成为依赖表达式(绑定到模板参数)才能延迟到实例化阶段检查。 - 我理解的依赖表达式判定规则:只要表达式依赖于模板参数,就算最终值不依赖该参数,也属于依赖表达式。比如
static_assert( (sizeof(T), false), "Type T is invalid" ),子表达式sizeof(T)让整个逗号表达式成为依赖表达式,哪怕它不影响最终结果。
但在下面的示例代码中,static_assert( make_dependent<I>(false), "Invalid value" )存在明显问题:模板参数I是非类型模板参数,而make_dependent仅接受类型模板参数,无论传入什么模板参数都无法消除这个不匹配,但这段代码却能正常编译。只有当if constexpr的这个分支被选中时,才会触发模板参数不匹配错误,而非static_assert直接失败。
这似乎是因为表达式中出现了依赖名称I,就会被延迟到模板实例化阶段处理;即便在实例化阶段,只要if constexpr未选中该块,这种恒存在的参数不匹配也会被接受。
核心问题
if constexpr未被选中的代码块在模板实例化阶段是否会被完全忽略?- 是否仅在模板定义阶段诊断语法错误和非依赖表达式的语义错误,即便依赖表达式可被静态判定为无论模板参数如何都存在可诊断的语义错误?
示例代码
#include <utility> // 使表达式依赖于任意模板类型参数 // 可用于延迟使用不完全类型,直到模板实例化阶段 // 可用于延迟static_assert( false, ... )直到模板实例化阶段 template<typename..., typename T> constexpr T&& make_dependent( T&& x ) { return std::forward<T>( x ); } struct incomplete; template<typename T = void> void defer_incomplete( incomplete& i ) { // i.method(); 错误:无法调用不完全类型的方法 make_dependent<T>( i ).method(); // 正常——延迟到模板实例化阶段 } template<int I> auto func() { if constexpr( I != 0 ) { return I; } else { // C++23之前会失败,因为static_assert条件为非依赖表达式 // static_assert( false, "Invalid value" ); // 使用恒为false的依赖static_assert条件 static_assert( I != 0, "Invalid value" ); // 巧妙使用逗号运算符使static_assert条件成为依赖表达式 static_assert( ( I, false ), "Invalid value" ); // 可正常编译,尽管make_dependent的模板参数始终为非类型 // 仅当if constexpr分支进入此处时,才会触发模板参数不匹配错误 static_assert( make_dependent<I>( false ), "Invalid value" ); } } struct incomplete { void method() {} }; int main() { incomplete i; defer_incomplete( i ); func<1>(); }
解答
问题1:未选中的代码块是否被完全忽略?
不会被完全忽略,但会进行有限的检查:
- 模板定义阶段:编译器会检查语法是否正确,同时对非依赖表达式进行语义检查(比如C++23前的
static_assert(false)属于非依赖表达式,会直接报错)。 - 模板实例化阶段:对于未选中分支中的依赖表达式,编译器会跳过依赖于模板参数的名称查找和语义检查。也就是说,只要错误是和模板参数绑定的,即便能提前预判到必然错误,只要分支没被选中,就不会触发诊断。
问题2:是否仅在定义阶段诊断语法和非依赖语义错误?
是的。具体规则:
- 模板定义阶段:仅诊断语法错误(比如括号不匹配)和非依赖表达式的语义错误(比如非依赖的
static_assert(false)、调用非依赖的不存在函数)。 - 对于依赖表达式,哪怕能静态判定无论模板参数如何都存在错误(比如示例中
make_dependent<I>的参数不匹配),只要它处于if constexpr未选中的分支,在实例化阶段也不会被诊断。只有当分支被选中时,才会对这些依赖表达式进行完整的语义检查。
示例中make_dependent<I>(false)的错误属于依赖语义错误(涉及非类型模板参数I与make_dependent的类型参数要求不匹配),所以在func<1>()实例化时,因为else分支未被选中,编译器不会检查这个表达式的有效性;只有当调用func<0>()时,else分支被选中,才会触发模板参数不匹配的错误。
内容的提问来源于stack exchange,提问作者leekillough
相关产品推荐
相关产品推荐

