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
.
中初始化
中找到相关的标准语
- 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 没有类型,它不符合上述要点,因此不能保证复制省略。
我一直在尝试生成一个 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
.
中找到相关的标准语
- 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 没有类型,它不符合上述要点,因此不能保证复制省略。