我在新版本的 gcc (4.9+) 上遇到了这个奇怪的编译错误。
代码如下:
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <map>
using namespace std;
struct ptrwrap
{
unique_ptr<int> foo;
};
template <typename T>
struct holder
{
holder() = default;
holder(const holder& b)
: t(b.t)
{
}
holder(holder&& b)
: t(std::move(b.t))
{
}
holder& operator=(const holder& h)
{
t = h.t;
return *this;
}
holder& operator=(holder&& h)
{
t = std::move(h.t);
return *this;
}
T t;
};
struct y_u_no_elision
{
holder<ptrwrap> elem;
};
typedef map<std::string, y_u_no_elision> mymap;
mymap foo();
int main()
{
auto m = foo();
m = foo();
return 0;
}
Here它在 ideone 上也有实际错误。基本上它归结为使用 ptrwrap 的删除复制构造函数。哪个……不应该发生。 map 按值返回(即移动),因此不能存在拷贝。
现在相同的代码在旧版本的 gcc(我试过 4.2 和 4.3)、我试过的所有 clang 版本以及 Visual Studio 2015 上都没有问题。
奇怪的是,如果我删除 holder 模板的显式复制和移动构造函数,它也会在 gcc 4.9+ 上编译。如果我将 map
更改为 vector
或 unordered_map
它也可以正常编译( here 是代码编译版本的链接使用 unordered_map
)
所以...这是 gcc 4.9 错误还是其他编译器允许我看不到的东西?我能做些什么而不涉及更改 holder
类吗?
最佳答案
简短回答:这是 libstdc++ 中的错误。根据标准中 [container.requirements.general] 中的分配器感知容器需求表(自 C++11 以来未更改),容器移动分配:
Requires: If
allocator_traits<allocator_type>::propagate_on_container_move_assignment::value
isfalse
,T
isMoveInsertable
intoX
andMoveAssignable
. [...]
(X
是容器类型,T
是它的 value_type
)
您正在使用默认分配器,它有 using propagate_on_container_move_assignment = true_type;
,所以上述要求不适用; value_type
应该没有特殊要求.
快速修复:如果您无法触摸 holder
,一种解决方案是更改 y_u_no_elision
, 添加
y_u_no_elision(const y_u_no_elision&) = delete;
y_u_no_elision(y_u_no_elision&&) = default;
长话短说:该错误主要是由 this line in stl_tree.h 引起的.
_Rb_tree
是 std::map
的底层实现并且其移动赋值运算符定义中的那一行基本上执行上面标准引用指定的检查。但是,它使用简单的 if
来实现,这意味着即使满足条件,另一个分支也必须编译,即使它不会在运行时执行。缺少 Shiny 的新 C++17 if constexpr
,这应该使用诸如标签分派(dispatch)之类的东西来实现(对于前两个条件 - 第三个是真正的运行时检查),以避免在所采用的分支之外实例化代码。
然后错误是由this line引起的,它使用 std::move_if_noexcept
在 value_type
.说来话长。
value_type
是std::pair<const std::string, y_u_no_elision>
.
在您的初始代码中:
-
holder
具有非删除、非 noexcept 复制和移动构造函数。 - 这意味着
y_u_no_elision
的相应隐式声明的构造函数也将是非删除和非 noexcept。 - 这些特征传播给
value_type
的构造函数. - 结果为
std::move_if_noexcept
返回const value_type&
而不是value_type&&
(如果可以,它会回退到复制 - 请参阅 these docs)。 - 这最终会导致
y_u_no_elision
的复制构造函数被调用,这将导致holder<ptrwrap>
要实例化的复制构造函数定义,它试图复制std::unique_ptr
.
现在,如果您从 holder
中删除用户声明的拷贝并移动构造函数和赋值运算符 :
-
holder
将得到隐式声明的。复制构造函数将被删除,移动构造函数将被默认,不删除和noexcept
. - 这会传播到
value_type
, 除了一个与holder
无关的异常(exception):value_type
的移动构造函数将尝试从const std::string
移动;这不会调用string
的移动构造函数(在本例中为noexcept
),而是它的复制构造函数,如string&&
无法绑定(bind)到const string
类型的右值. -
string
的复制构造函数不是noexcept
(它可能必须分配内存),所以value_type
的移动构造函数也不会。 - 那么,为什么代码可以编译?因为
std::move_if_noexcept
背后的逻辑: 它返回一个右值引用,即使参数的移动构造函数不是noexcept
,只要参数不是可复制构造的(如果不能回退到复制,它会回退到非 noexcept 移动);和value_type
不是,因为holder's
删除复制构造函数。
这是上面快速修复背后的逻辑:你必须做一些事情才能使 value_type
有一个有效的移动构造函数和一个删除的复制构造函数,以便从 move_if_noexcept
获得右值引用.这是因为您将无法制作 value_type
有一个noexcept
由于 const std::string
移动构造函数,如上所述。
关于c++ - 在新版本的 gcc 上返回隐式不可复制结构的 std::map 时出现编译错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39040609/