这个问题是由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中的任何一个,则两个编译器都接受此代码。如果将#1替换为int b = 0;both compilers reject it

我的问题是:
  • 根据当前标准,哪个编译器正确?
  • 如果GCC是正确的,为什么用int b = 0;替换#1会使该代码格式错误?如果Clang是正确的,为什么删除#2,#3或#4中的任何一个使该代码格式正确?
  • 最佳答案

    由于这两个用户定义的构造函数都是模板,因此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);
    }
    

    Clang和MSVC的Rejected,已被gcc接受;取消注释#1,所有三个选项均接受。

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

    第2部分:

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

    删除#2将为S提供用户定义的constexpr复制构造函数,然后将其委托(delegate)给#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&)不会抑制隐式定义的拷贝构造函数,并且永远不会优于隐式定义的拷贝构造函数,因为它是模板并且参数列表相同。

    关于c++ - 常量表达式中的模板化委派拷贝构造函数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60472257/

    10-14 21:50
    查看更多