【问题标题】:Templated delegating copy constructor in constant expressions常量表达式中的模板化委托复制构造函数
【发布时间】:2020-06-13 18:28:39
【问题描述】:

这个问题的动机是this one

考虑以下代码:

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 拒绝了它 (Example on 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 中的任何一个,两个编译器都会接受此代码。如果我们用int b = 0;替换#1,both compilers reject it

我的问题是:

  1. 根据当前标准,哪个编译器是正确的?
  2. 如果 GCC 是正确的,为什么用 int b = 0; 替换 #1 会使这段代码格式错误?如果 Clang 是正确的,为什么删除 #2、#3 或 #4 中的任何一个会使这段代码格式正确?

【问题讨论】:

  • 顺便说一句,这些函数都不是复制构造函数。根据定义,构造函数只有在不是模板时才是复制构造函数。
  • @NicolBolas 感谢您指出这一点。我只在标题中使用“复制构造函数”来总结问题。如果您有更好的标题,请随时编辑。

标签: c++ templates language-lawyer copy-constructor constant-expression


【解决方案1】:

由于您的两个用户定义的构造函数都是模板,they are not 复制(或移动)构造函数。所以编译器隐式声明了一个拷贝构造函数,并将其定义为默认值。

因此,第 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);
}

Rejected 来自 Clang 和 MSVC,被 gcc 接受;取消注释#1 以接受所有三个。

根据the implicitly-defined copy constructor 的定义,#1constexpr A(A const&amp;) = default; 没有任何不同,因此 gcc 是正确的。另请注意,如果我们给 B 一个用户定义的 constexpr 复制构造函数 Clang 和 MSVC 再次接受,那么问题似乎是这些编译器无法跟踪递归空隐式可复制类的 constexpr 复制构造性。为 MSVCClang 提交了错误(fixed 用于 Clang 11)。

第 2 部分:

删除 #1 意味着您正在复制(执行左值到右值转换)类型为 int 的对象 s.b,其生命周期开始于 constexpr 上下文之外。

删除#2 会为S 提供用户定义的constexpr 复制构造函数,然后将其委托给#4

删除#3S 提供了一个用户定义的(非常量)复制构造函数,抑制了隐式定义的复制构造函数,因此委托构造调用模板 const 构造函数(请记住,它不是复制构造函数)。

删除#4 意味着您的带有参数S&amp; other 的构造函数模板不再调用隐式定义的复制构造函数,因此b 是默认初始化的,Clang 可以在 constexpr 上下文中执行此操作。请注意,复制构造函数仍被隐式声明并定义为默认值,只是您的构造函数 template&lt;class...&gt; S::S(S&amp; other) 是重载决议的首选。

重要的是要认识到抑制隐式定义的复制构造函数和提供首选重载之间的区别。 template&lt;class...&gt; S::S(S&amp;) 不会抑制隐式定义的复制构造函数,但它是非 const 左值参数的首选,假设隐式定义的复制构造函数具有参数 S const&amp;。另一方面,template&lt;class...&gt; S::S(S const&amp;) 不会抑制隐式定义的复制构造函数,并且永远不能优先于隐式定义的复制构造函数,因为它是一个模板并且参数列表是相同的。

【讨论】:

  • 您能否详细解释一下“递归空隐式可复制类”,“递归空”是什么意思?
  • @xskxzr 的“递归空”我的意思是一个非多态类类型,所有的非静态数据成员和直接基都是递归空的。换句话说,它的任何地方都没有标量对象,所以它的值表示是空的——它是无状态的。 “隐式可复制”是指它的复制构造函数不是用户提供的,并且它的所有直接基础都是隐式可复制的,因此它的复制构造函数不会调用任何用户提供的代码。 (1/2)
  • 把这些放在一起,你就有了一个类,它的复制构造函数是完全无操作的,所以即使源对象的生命周期在上下文之外也可以在 constexpr 上下文中调用。 (2/2)
猜你喜欢
  • 2018-01-08
  • 2013-02-22
  • 2015-02-10
  • 2011-05-24
  • 1970-01-01
  • 2020-09-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多