c++ - 常量表达式中的模板化委托复制构造函数
问题描述
这个问题是由这个引起的。
考虑以下代码:
struct B {};
struct S {
B b; // #1
S() = default;
template <typename ...dummy> // #2
constexpr S(const S&) {}
template <typename ...dummy> // #3
constexpr S(S &other)
: S(const_cast<const S&>(other)) // #4
{}
};
S s;
constexpr S f() {return s;}
int main() {
constexpr auto x = f();
}
GCC 成功编译了这段代码,但 Clang 拒绝了它(Godbolt.org 上的示例)。Clang 产生的错误信息是
<source>:21:20: error: constexpr variable 'x' must be initialized by a constant expression
constexpr auto x = f();
^ ~~~
<source>:13:11: note: read of non-constexpr variable 's' is not allowed in a constant expression
: S(const_cast<const S&>(other))
^
<source>:13:11: note: in call to 'S(s)'
<source>:18:25: note: in call to 'S(s)'
constexpr S f() {return s;}
^
<source>:21:24: note: in call to 'f()'
constexpr auto x = f();
^
<source>:17:3: note: declared here
S s;
^
请注意,如果我们删除 #2、#3 或 #4 中的任何一个,两个编译器都会接受此代码。如果我们将 #1 替换为int b = 0;
,两个编译器都会拒绝它。
我的问题是:
- 根据当前标准,哪个编译器是正确的?
- 如果 GCC 是正确的,为什么替换 #1
int b = 0;
会使这段代码格式错误?如果 Clang 是正确的,为什么删除 #2、#3 或 #4 中的任何一个会使这段代码格式正确?
解决方案
由于您的用户定义的构造函数都是模板,因此它们不是复制(或移动)构造函数。因此编译器隐式声明了一个复制构造函数,并将其定义为默认值。
因此,第 1 部分归结为以下区别程序:
struct A {
struct B {} b;
constexpr A() {};
// constexpr A(A const& a) : b{a.b} {} // #1
};
int main() {
auto a = A{};
constexpr int i = (A{a}, 0);
}
被 Clang 和 MSVC拒绝,被 gcc 接受;取消注释#1
所有三个接受。
根据隐式定义的复制构造函数的定义,没有#1
任何不同的方式,constexpr A(A const&) = default;
因此 gcc 是正确的。还要注意,如果我们给B
用户定义的 constexpr 复制构造函数 Clang 和 MSVC 再次接受,那么问题似乎是这些编译器无法跟踪递归空隐式可复制类的 constexpr 复制构造性。提交了MSVC和Clang的错误(已针对 Clang 11修复)。
第2部分:
删除#1
意味着您正在复制(执行左值到右值转换)s.b
类型的对象int
,其生命周期开始于 constexpr 上下文之外。
删除#2
提供S
了一个用户定义的constexpr
复制构造函数,然后将其委托给 at #4
。
删除#3
提供S
了一个用户定义的(非 const)复制构造函数,抑制了隐式定义的复制构造函数,因此委托构造调用模板 const 构造函数(记住,它不是复制构造函数)。
删除#4
意味着您的带参数的构造函数模板S& other
不再调用隐式定义的复制构造函数,b
默认初始化也是如此,Clang 可以在 constexpr 上下文中执行此操作。请注意,复制构造函数仍被隐式声明并定义为默认值,只是template<class...> S::S(S& other)
重载决议首选您的构造函数。
重要的是要认识到抑制隐式定义的复制构造函数和提供首选重载之间的区别。template<class...> S::S(S&)
不抑制隐式定义的复制构造函数,但它是非 const 左值参数的首选,假设隐式定义的复制构造函数具有参数S const&
。另一方面,template<class...> S::S(S const&)
不会抑制隐式定义的复制构造函数,并且永远不能优先于隐式定义的复制构造函数,因为它是一个模板并且参数列表是相同的。
推荐阅读
- python - 我的标点符号和字符的情节只是没有出现。需要建议
- python - 如何在 python 中对多个分类特征进行一次热编码,而不会被困在虚拟变量中
- javascript - 如何存储和访问元素上的数据属性,例如 React 中的选择选项
- c++ - const 引用是否绑定到另一个从临时悬空引用投射的引用?
- python - 无法在 Ubuntu 中安装正则表达式
- excel - 给定两个日期之间的天数,将一个月中的天数视为固定的 30 天
- php - 为什么 laravel (eloquent) 缓存来自 api 路由的最后响应?
- windows - 如何在 Windows 中启动并运行 Github 应用程序“vehicle-tracker-api”
- r - 创建第二列中的多列值,并根据第一列用出现次数填充新数据框
- android - 如果位置服务被禁用,getFusedLocationProviderClient 不会激活我的 GPS