G++ 7.1.0 及更高版本支持此构造函数中的保证复制省略,但 Clang++ 4.0 及更高版本不支持

G++ 7.1.0 onwards supports guaranteed copy elision in this constructor, but Clang++ 4.0 onwards do not

我一直在尝试生成一个 class,其成员是不可复制类型的 std::array,我需要在构造函数中对其进行初始化。参考 上的答案,我曾认为以下方法可行:

#include <array>
#include <utility>

class Foo {
public:
    Foo() {}
    Foo(const Foo& rhs) = delete;   
    Foo(Foo&& rhs) = delete;
};

template<size_t BufferSize>
class FooBuffer {
public:

    template<size_t... Is>
    FooBuffer(std::index_sequence<Is...>)
        : _buffer({{(static_cast<void>(Is), Foo{})...}})
    {
    }

    FooBuffer() : FooBuffer(std::make_index_sequence<BufferSize>{}) {}

private:    

    std::array<Foo,BufferSize> _buffer;

};

using namespace std;
int main(int, char **) {
    FooBuffer<10> foo;
}

根据 Wandbox 的说法,从 GCC 7.1.0 版开始,使用 C++17 或 C++17(GNU) 标志也是如此:

link to working compilation under GCC 7.1.0

但是,尽管从版本 4 开始为 Clang++ 列出了保证复制省略支持,但我找不到接受上述代码的版本,直到 9.0 或当前的 HEAD:

link to compiler error under Clang

产生的错误与数组的不可复制构造性有关_buffer:

prog.cc:18:5: error: call to implicitly-deleted copy constructor of 'std::array<Foo, 10UL>'
                : _buffer({{(static_cast<void>(Is), Foo{})...}})
                  ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:22:16: note: in instantiation of function template specialization 'FooBuffer<10>::FooBuffer<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>' requested here
        FooBuffer() : FooBuffer(std::make_index_sequence<BufferSize>{}) {}
                      ^
prog.cc:32:16: note: in instantiation of member function 'FooBuffer<10>::FooBuffer' requested here
        FooBuffer<10> foo;
                      ^
/opt/wandbox/clang-head/include/c++/v1/array:143:9: note: copy constructor of 'array<Foo, 10>' is implicitly deleted because field '__elems_' has a deleted copy constructor
    _Tp __elems_[_Size];
        ^
prog.cc:8:2: note: 'Foo' has been explicitly marked deleted here
        Foo(const Foo& rhs) = delete;   
        ^
1 error generated.

这是否是编译器之间对保证复制省略的实现存在分歧?或者,我在这里看到关于 SO 的迹象表明不能依赖保证的复制省略,并且对于编译器来说是完全可选的。这适用于此处,还是仅适用于存在复制构造函数且正确代码不需要 GCE 的情况?

T(args...)是直接初始化。对于 class 类型的直接初始化,复制省略只有在参数是 class 类型时才能保证。但是在你的例子中,参数是一个大括号初始化列表。

GCC 未能诊断问题是违反标准的。

这可以通过显式创建一个不会被具体化的临时文件来解决(尽管看起来很反常):

: _buffer(std::array{(static_cast<void>(Is), Foo{})...})

或者简单地使用列表初始化:

 _buffer{(static_cast<void>(Is), Foo{})...}

当你这样做时

_buffer({{(static_cast<void>(Is), Foo{})...}})

{(static_cast<void>(Is), Foo{})...} 部分为一个对象构建一个 braced-init-list。外层 {} 是用于创建 braced-init-list 引用的对象的大括号。由于这些列表中的 none 具有类型,因此编译器必须枚举 _buffer 的构造函数以找出要调用的内容。当编译器执行此操作时,发现的只是隐式生成的复制和移动构造函数。由于 Foo 不是 copy/move-able,因此这些被隐式删除,这意味着没有可以调用的构造函数。

如果你切换到

_buffer{(static_cast<void>(Is), Foo{})...}

然后您可以直接初始化 _buffer,这保证可以工作,因为 Foo{} 纯右值不会被复制,而是直接在适当的位置创建。

您也可以改用

_buffer(std::array<Foo,BufferSize>{{(static_cast<void>(Is), Foo{})...}})

这会起作用,因为现在你确实有一个类型为 std::array<Foo,BufferSize> 的纯右值,而不是被复制,而是直接在 _buffer.

中初始化

可在 [dcl.init]/17

中找到相关的标准语
  • If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.

强调我的

由于 braced-init-list 没有类型,它不符合上述要点,因此不能保证复制省略。