GCC14+C++20下原子约束自依赖编译错误及非explicit替代方案咨询
GCC 14 C++20编译失败问题分析与解决
问题重现
以下代码在GCC 14中使用-std=c++20编译失败,但在GCC 13及更早版本、-std=c++17下可正常编译;给Bar的构造函数添加explicit关键字也能解决编译错误:
#include <tuple> #include <type_traits> struct Foo { template<typename T> Foo(T) {} }; struct Bar { Bar(std::tuple<Foo>) {} }; int main() { return std::is_constructible_v<Foo, Bar>; }
补充背景:该问题最初在复制Bar对象时发现:
Bar b1{Foo{0}}; Bar b2{b1};
编译错误信息
tuple:979:42: error: satisfaction of atomic constraint '__constructible<_UTypes ...>() [with _Elements = {_Elements ...}; _UTypes = {_UTypes ...}]' depends on itself tuple:989:42: error: 'static consteval bool std::tuple< <template-parameter-1-1> >::__constructible() [with _UTypes = {Bar}; _Elements = {Foo}]' called in a constant expression before its definition is complete tuple:989:42: error: satisfaction value of atomic constraint '__constructible<_UTypes ...>() [with _Elements = {Foo}; _UTypes = {Bar}]' changed from '<expression error>' to 'true'
问题成因
- C++20约束检查严格化:GCC 14对
std::tuple的构造函数实现了更严格的C++20概念约束,新增__constructible原子约束用于验证参数能否构造tuple的元素。 - 循环依赖的隐式转换检查:
- 编译器判断
std::is_constructible_v<Foo, Bar>时,因Foo的模板构造函数接受任意类型,理论上可直接用Bar构造Foo。 - 但
Bar存在非explicit的构造函数接受std::tuple<Foo>,编译器会尝试所有隐式转换路径,包括检查Bar能否隐式转为std::tuple<Foo>。 - 这会触发
std::tuple<Foo>的构造约束检查:验证能否用Bar构造tuple的元素Foo,也就是回到std::is_constructible_v<Foo, Bar>的判断,形成循环依赖,导致约束检查失败。
- 编译器判断
- 旧版本编译正常的原因:GCC 13及更早版本的
std::tuple未实现该原子约束,C++17也没有这种严格的概念检查逻辑,因此不会触发循环依赖报错。
除添加explicit外的解决方案
- 限制Foo模板构造函数的参数类型:通过概念或SFINAE排除
Bar类型,阻断错误转换路径:struct Foo { template<typename T> requires (!std::is_same_v<T, Bar>) Foo(T) {} }; - 将Bar的构造函数改为模板并添加约束:避免编译器将
Bar与std::tuple<Foo>建立隐式转换关联:struct Bar { template<typename T> requires std::is_same_v<T, std::tuple<Foo>> Bar(T) {} }; - 显式删除Foo的Bar类型构造函数:如果不需要用
Bar构造Foo,直接禁用该路径:struct Foo { template<typename T> Foo(T) {} Foo(Bar) = delete; }; - 降级编译标准:如果项目允许,使用
-std=c++17编译代码。
内容的提问来源于stack exchange,提问作者Kane
相关产品推荐
相关产品推荐

