为何C++20中Oneable概念的requires表达式实现存在跨编译器差异?
C++20中三种Oneable概念实现的跨编译器差异解析
在C20中,我们尝试用三种不同的requires表达式实现"Oneable"概念(要求类型能被1初始化/赋值),但在x86-64 GCC 12.2、x86-64 Clang 15.0.0、x64 MSVC 19.33三款编译器上表现不一致。这些差异的核心是不同编译器对C标准中**即席上下文(immediate context)**范围的判定逻辑不同——只有即席上下文中的错误会被视为"概念不满足",非即席上下文的错误会直接触发编译报错。
1. 简单赋值表达式写法(三款编译器均正常编译)
这种写法是requires表达式的常规用法,直接检查赋值操作的有效性:
// `Oneable` implemented using `requires` with a simple assignment expression // clang, gcc, and msvc all compile template <class T> concept Oneable = requires(T n) { n = 1; }; static_assert(Oneable<int>); static_assert(!Oneable<int*>);
解析
n = 1是直接的表达式要求,编译器会在即席上下文中检查该操作的有效性:
- 当
T=int时,赋值合法,概念满足; - 当
T=int*时,int* = 1的赋值操作无效,属于即席上下文内的错误,编译器将其视为概念不满足,而非硬错误。
三款编译器对这种直接表达式要求的判定逻辑一致,因此都能正常编译。
2. Lambda体内赋值语句写法(Clang/ MSVC可编译,GCC报错)
将初始化逻辑放在lambda体内,GCC在实例化Oneable<int*>时触发硬错误:
// `Oneable` implemented using `requires` with an assignment statement inside // the body of a lambda expression // clang and msvc compile // gcc gives a compiler error on instantiating Oneable<T*> template <class T> concept Oneable = requires { []() { T n = 1; }; }; static_assert(Oneable<int>); static_assert(!Oneable<int*>);
GCC报错信息:
<source>: In lambda function: <source>:8:43: error: invalid conversion from 'int' to 'int*' [-fpermissive] 8 | concept Oneable = requires { []() { T n = 1; }; }; | ^ | |
解析
分歧点在于lambda函数体的实例化时机:
- Clang/MSVC:认为在requires表达式中,仅需检查lambda的定义是否合法(语法上无问题),lambda的函数体属于延迟实例化的部分,只有当lambda被调用时才会实例化。因此当判断
Oneable<int*>是否满足时,函数体内int* n=1的错误属于即席上下文,被视为概念不满足,static_assert(!Oneable<int*>)正常通过。 - GCC:认为在实例化外部模板
Oneable<int*>时,lambda的函数体也会被立即实例化,此时int* n=1的错误属于非即席上下文,直接触发编译报错。
3. 结构体实例化写法(GCC/MSVC可编译,Clang报错)
通过结构体默认构造检查初始化有效性,Clang在实例化Oneable<int*>时触发硬错误:
// `Oneable` implemented using `requires` with an instantiation of a struct // gcc and msvc compile // clang gives a compiler error on instantiating Oneable<T*> template <class T> struct S { T n = 1; }; template <class T> concept Oneable = requires { S<T>{}; }; static_assert(Oneable<int>); static_assert(!Oneable<int*>);
Clang报错信息:
<source>:8:18: error: cannot initialize a member subobject of type 'int *' with an rvalue of type 'int' struct S { T n = 1; }; ^ <source>:11:35: note: in instantiation of default member initializer 'S<int *>::n' requested here concept Oneable = requires { S<T>{}; }; ^ <source>:11:30: note: in instantiation of requirement here concept Oneable = requires { S<T>{}; }; ^~~~~~ <source>:11:19: note: while substituting template arguments into constraint expression here concept Oneable = requires { S<T>{}; }; ^~~~~~~~~~~~~~~~~~~~ <source>:15:18: note: while checking the satisfaction of concept 'Oneable<int *>' requested here std::cout << Oneable<int*> << '\n'; ^~~~~~~~~~~~~
解析
分歧点在于结构体默认成员初始化器的实例化是否属于即席上下文:
- GCC/MSVC:认为在检查
S<T>{}的有效性时,默认成员初始化器T n=1的错误属于即席上下文,因此将Oneable<int*>视为不满足,static_assert(!Oneable<int*>)正常通过。 - Clang:认为实例化
S<int*>时,默认成员初始化器的错误属于非即席上下文,直接触发编译报错。
内容的提问来源于stack exchange,提问作者canardanonyme
相关产品推荐
相关产品推荐

