常量表达式中的模板化委派副本构造函数 [英] Templated delegating copy constructor in constant expressions

查看:99
本文介绍了常量表达式中的模板化委派副本构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题由考虑以下代码:

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产生的错误消息是

GCC compiles this code successfully, but Clang rejects it (Example on Godbolt.org). The error message produced by Clang is

<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;,则两个编译器都会拒绝它.

Note if we remove any of #2, #3 or #4, both compilers accept this code. If we replace #1 with int b = 0;, both compilers reject it.

我的问题是:

  1. 根据当前标准,哪个编译器是正确的?
  2. 如果GCC是正确的,为什么用int b = 0;替换#1会使该代码格式错误?如果Clang是正确的,为什么删除#2,#3或#4中的任何一个使该代码格式正确?
  1. Which compiler is correct according to the current standard?
  2. If GCC is correct, why does replacing #1 with int b = 0; make this code ill-formed? If Clang is correct, why does removing any of #2, #3 or #4 make this code well-formed?

推荐答案

由于两个用户定义的构造函数都是模板,因此

Since both your user-defined constructors are templates, they are not copy (or move) constructors. So the compiler implicitly declares a copy constructor, and defines it as defaulted.

第1部分因此归结为以下区别程序:

Part 1 thus comes down to the following distinguishing program:

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,让所有三个人接受.

Rejected by Clang and MSVC, accepted by gcc; uncomment #1 for all three to accept.

根据隐式定义的副本构造函数的定义 #1constexpr A(A const&) = default;没有任何区别,因此gcc是正确的.还要注意,如果我们给B一个用户定义的constexpr复制构造函数Clang和MSVC再次接受,那么问题似乎是这些编译器无法跟踪递归空隐式可复制类的constexpr复制构造性. MSVC 的已提交错误a>和 C语(

Per the definition of the implicitly-defined copy constructor there is no way that #1 is any different to constexpr A(A const&) = default; so gcc is correct. Note also that if we give B a user-defined constexpr copy constructor Clang and MSVC again accept, so the issue appears to be that these compilers are unable to track the constexpr copy constructibility of recursively empty implicitly copyable classes. Filed bugs for MSVC and Clang (fixed for Clang 11).

第2部分:

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

Removing #1 means that you are copying (performing lvalue-to-rvalue conversion on) an object s.b of type int, whose lifetime began outside constexpr context.

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

Removing #2 gives S a user-defined constexpr copy constructor, which is then delegated to at #4.

删除#3将为S提供用户定义的(非const)副本构造函数,从而取消隐式定义的副本构造函数,因此委派构造将调用模板const构造函数(请记住,它不是副本构造函数) ).

Removing #3 gives S a user-defined (non-const) copy constructor, suppressing the implicitly-defined copy constructor, so the delegating construction invokes the template const constructor (which, remember, is not a copy constructor).

删除#4意味着带有参数S& other的构造函数模板不再调用隐式定义的副本构造函数,因此b是默认初始化的,Clang可以在constexpr上下文中进行此操作.请注意,副本构造函数仍被隐式声明并定义为默认值,只是重载解析会首选您的构造函数template<class...> S::S(S& other).

Removing #4 means that your constructor template with argument S& other no longer calls the implicitly-defined copy constructor, so b is default-initialized, which Clang can do in constexpr context. Note that a copy constructor is still implicitly declared and defined as defaulted, it is just that your constructor template<class...> S::S(S& other) is preferred by overload resolution.

重要的是要认识到抑制隐式定义的副本构造函数与提供首选重载之间的区别. template<class...> S::S(S&)不会抑制隐式定义的副本构造函数,但是对于非const左值参数,它是首选的,前提是隐式定义的副本构造函数具有参数S const&.另一方面,template<class...> S::S(S const&)不会抑制隐式定义的副本构造函数,并且永远不会优于隐式定义的副本构造函数,因为它是模板并且参数列表相同.

It is important to recognize the distinction between suppressing the implicitly-defined copy constructor and providing a preferred overload. template<class...> S::S(S&) does not suppress the implicitly-defined copy constructor, but it is preferred for non-const lvalue argument, assuming that the implicitly-defined copy constructor has argument S const&. On the other hand, template<class...> S::S(S const&) does not suppress the implicitly-defined copy constructor, and can never be preferred to the implicitly-defined copy constructor since it is a template and the parameter-lists are the same.

这篇关于常量表达式中的模板化委派副本构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆