C++ 不可移动对象数组的初始化:为什么这样的代码无法在GCC上编译?

C++ 不可移动对象数组的初始化:为什么这样的代码无法在GCC上编译?,c++,arrays,gcc,language-lawyer,C++,Arrays,Gcc,Language Lawyer,下面是一个代码示例,其中Test是一个不可复制和不可移动的类,具有一些virtual成员和用户定义的构造函数,B是一个包含Test对象的原始(C样式)数组的类: class Test { public: Test() = delete; Test(const Test&) = delete; Test(Test&&) = delete; Test& operator=(const Test&) = delete;

下面是一个代码示例,其中
Test
是一个不可复制和不可移动的类,具有一些
virtual
成员和用户定义的构造函数,
B
是一个包含
Test
对象的原始(C样式)数组的类:

class Test
{
public:
    Test() = delete;

    Test(const Test&) = delete;
    Test(Test&&) = delete;
    Test& operator=(const Test&) = delete;
    Test& operator=(Test&&) = delete;

    Test(int a, int b) : a_(a), b_(b) {}
    virtual ~Test() {}

    int a_;
    int b_;
};

//----------------

class B
{
public:
/*(1)*/ B() : test_{{1, 2}, {3, 4}} {} // Does not compile on GCC, but compiles on Clang and MSVC

private:
        Test test_[2];
};

//---------------- 

int main()
{
        B b;
/*(2)*/ Test test[2] = {{1, 2}, {3, 4}}; // Successfully compiles on GCC, Clang and MSVC
}
我想使用括号内的初始化语法(第
/*1*/
行)初始化
B
的内部数组
test
,这样两个
test
对象中的每一个都将被构造到位,而无需创建临时对象然后移动它

在Clang和MSVC上,此代码编译时没有警告

但GCC的行为让我感到困惑:它无法编译行
/*1*/
,而成功编译行
/*2*/
,我使用相同的语法初始化本地数组。然而,对于编译第一行,它仍然需要类
Test
的已删除move构造函数

问题是,为什么?是否C++标准明确地定义了这些行<代码> /* 1 */< /COD>和<代码> /* 2 */< /COD>是否应该编译?如果是,从标准的角度来看,哪种编译器是正确的?这种不一致的行为会被称为GCC错误吗,或者Clang和MSVC会忽略他们应该执行的一些检查吗

我可以理解,GCC可能需要一个move构造函数,以便从内部大括号(
{1,2}
)创建一个临时的
Test
对象,然后将该对象移动到数组中。因此出现了编译错误。但如果是这样,为什么它不会因为同样的原因在
/*(2)*/
行失败呢?在这个例子中,这是我最困惑的事情


顺便说一句,这里有一个有趣的观察结果:如果我用
std::array
(而不是“C风格”数组)替换
test
的定义,并用
test{{{{{1,2},{3,4}}}
替换构造函数初始化列表中的代码,那么上述三个编译器上的所有代码都开始成功编译

我也不清楚为什么GCC在这种情况下在任何一行上都不会失败,而在使用“原始”数组时却失败了


有人能解释一下吗?

我认为初始化没有问题,所以我认为这是一个GCC错误


涉及的初始化是列表初始化,因此我们参考:

定义了
T
类型的对象或引用的列表初始化 详情如下:

  • [……]

  • ()否则,如果
    T
    是聚合,则聚合初始化为 表演

  • [……]

(数组是一个集合。)现在我们转到:

当聚合由指定的初始值设定项列表初始化时 在[dcl.init.list]中,初始值设定项列表的元素被视为 集合元素的初始值设定项,按顺序每个 元素是从相应的 initializer子句。如果initializer子句是一个表达式,并且需要进行窄化转换才能转换表达式,则 程序格式不正确


因此,对于这两个元素中的任何一个,我们都在有效地执行
testa={1,2}
,这是有效的,因为
Test(int,int)
是不显式的。因此,初始化是格式良好的,应该被编译器接受。

相关,几乎重复:如果
Test
没有虚拟成员,它也可以用g++编译:@chtz看起来很相似,但我在那里找不到问题的答案——只有一些建议的解决方法(我不感兴趣,因为我已经找到了)所以我希望有人解释为什么在我的例子中构造函数无法编译在GCC上,而在代码>主代码中数组的初始化是成功编译的,并且这是正确的,其他两个编译器在没有警告的情况下编译这个代码。为什么不一致,C++标准告诉了它什么?(如果是的话),哪个编译器是对的,等等。@chtz-True,虚拟成员起作用。我会相应地更新这个问题,让它更清楚。@chtz-BTW,这里重要的不是
virtual
关键字,而是析构函数是用户定义的。非虚拟
~Test(){}
也不编译。但是
~Test()=default;
编译成功。Hovewer,
virtual~Test()=default;
失败并出现相同错误。