C++20函数参数用于非类型模板参数常量表达式的合规性问询
函数参数作为非类型模板参数常量表达式的场景与C++20规定分析
核心问题
- 函数参数可作为非类型模板参数的常量表达式组成部分的场景是什么?
- 未求值操作数是否会影响预期行为?
- C++20标准对以下示例代码的有效性有何规定?
示例代码
struct t { static constexpr auto b = true; }; template<auto> inline constexpr auto v = true; // clang ok, gcc nope, msvc ok constexpr auto f(t x) -> decltype(v<x.b>) { return true; } static_assert(f({})); // clang ok, gcc ok, msvc nope constexpr auto g(t x) -> decltype([]<auto = x.b>{}(), true) { return true; } static_assert(g({})); // clang nope, gcc ok, msvc nope constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) { return true; } static_assert(h(t{}));
各编译器错误信息
GCC针对函数f的错误
<source>:5:36: error: 缺少模板参数,位于'<'标记之前 5 | constexpr auto f(t x) -> decltype(v<x.b>) | ^ <source>:5:41: error: ')'标记之前应为主表达式 5 | constexpr auto f(t x) -> decltype(v<x.b>) | ^ <source>:7:15: error: 作用域中未声明'f' 7 | static_assert(f({})); | ^
MSVC针对函数g的错误
<source>(10): error C2065: 'x': 未声明的标识符 <source>(10): error C3889: 调用类类型对象'<lambda_1>':未找到匹配的调用运算符 <source>(10): note: 可能为'auto <lambda_1>::operator ()(void) const' <source>(10): note: 'auto <lambda_1>::operator ()(void) const': 无法推导模板参数'_T1' <source>(10): note: '.'的左侧必须是类/结构体/联合体 <source>(10): note: 类型为'unknown-type' <source>(10): error C3615: constexpr函数'g'无法生成常量表达式 <source>(10): note: 失败原因是类型'unknown-type'不是字面类型 <source>(12): error C2131: 表达式未计算为常量 <source>(12): note: 遇到了非常量(子)表达式
Clang针对函数h的错误
<source>:15:48: error: 未指定默认捕获方式的lambda无法隐式捕获变量'x' 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ <source>:15:38: note: 在此处代入lambda表达式时 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ <source>:17:15: note: 在此处代入推导的模板参数到函数模板'h'时 [with x:auto = t] 17 | static_assert(h(t{})); | ^ <source>:15:23: note: 'x'声明于此 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ <source>:15:38: note: lambda表达式始于此处 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ <source>:15:39: note: 按值捕获'x' 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ | x <source>:15:39: note: 按引用捕获'x' 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ | &x <source>:15:39: note: 默认按值捕获 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ | = <source>:15:39: note: 默认按引用捕获 15 | constexpr auto h(auto x) -> decltype([]<bool = x.b>{}(), true) | ^ | &
MSVC针对函数h的错误
<source>(15): error C2065: 'x': 未声明的标识符 <source>(17): error C2672: 'h': 未找到匹配的重载函数 <source>(15): note: 可能为'unknown-type h(_T0)' <source>(17): note: 无法特化函数模板'unknown-type h(_T0)' <source>(17): note: 使用以下模板参数: <source>(17): note: '_T0=t' <source>(15): note: 调用类类型对象'<lambda_2>':未找到匹配的调用运算符 <source>(15): note: 可能为'auto <lambda_2>::operator ()(void) const'
问题解答
1. 函数参数作为非类型模板参数常量表达式组成部分的场景
根据C++20标准,核心前提是该参数在当前上下文是常量表达式,且其值在模板实例化时可被编译器确定,具体场景包括:
- 参数是字面类型对象,且用于模板参数的部分是该对象的
static constexpr成员(如示例中x.b,本质等价于t::b) - 参数是
constexpr函数的形参,调用时传入的实参是常量表达式(如f({})中的临时t对象) - 上下文处于未求值操作数中(如
decltype内),此时参数无需实际求值,但必须满足常量表达式的语法与类型要求。
2. 未求值操作数对预期行为的影响
未求值操作数(如decltype的操作数)不会触发实际求值,但编译器仍会执行:
- 语法与类型检查
- 常量表达式合法性验证
- 模板参数推导等逻辑
即使表达式不会被执行,若不符合常量表达式规则,编译器仍会报错,不会因“未求值”而忽略错误。
3. C++20标准对三个示例代码的规定
函数f
x.b是struct t的static constexpr成员,访问时不依赖对象实例,等价于t::b,属于合法的常量表达式。因此这段代码符合C++20标准,GCC的报错属于实现问题。
函数g
lambda模板的默认参数auto = x.b本质是t::b,属于常量表达式,且处于未求值的decltype上下文中,模板实例化规则允许该写法。这段代码符合C++20标准,MSVC的报错属于实现缺陷。
函数h
函数模板形参x的类型实例化为t后,x.b仍等价于t::b,理论上不依赖x对象本身,无需捕获。但Clang要求显式捕获外部变量(即使访问的是静态成员),导致报错。从标准角度,代码本身合法,Clang的报错属于实现严格性差异。
内容的提问来源于stack exchange,提问作者303
相关产品推荐
相关产品推荐

