在新版本的 gcc 上返回隐式不可复制结构的 std::map 时出现编译错误
Compilation error when returning an std::map of implicitly non-copyable structs on new versions of gcc
我在新版本的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;
}
在这里,它也在具有实际错误的 ideone 上。基本上它归结为使用 ptrwrap 的已删除复制构造函数。哪。。。不应该发生。映射按值返回(即移动(,因此不能存在任何副本。
现在,在旧版本的gcc(我尝试了4.2和4.3(,我尝试过的所有版本的clang以及Visual Studio 2015上编译相同的代码没有问题。
奇怪的是,如果我删除持有者模板的显式副本并移动构造函数,它也会在 gcc 4.9+ 上编译。如果我将map
更改为vector
或unordered_map
它也可以很好地编译(这是指向带有unordered_map
的代码编译版本的链接(
那么......这是一个gcc 4.9错误,还是其他编译器允许我看不到的东西?我能做些什么不涉及更改holder
类?
简短的回答:这是libstdc++中的一个错误。根据标准中 [container.requirements.general] 中的分配器感知容器要求表(自 C++11 以来未更改(,容器移动分配:
要求:如果
allocator_traits<allocator_type>::propagate_on_container_move_assignment::value
false
,T
被MoveInsertable
成X
和MoveAssignable
。 [...]
(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;
长话短说:该错误基本上是由 stl_tree.h 中的这一行引起的。
_Rb_tree
是std::map
的基础实现,其移动分配运算符定义中的该行基本上执行上述标准报价指定的检查。但是,它使用一个简单的if
来完成,这意味着,即使满足条件,另一个分支也必须编译,即使它不会在运行时执行。缺少闪亮的新 C++17 if constexpr
,这应该使用类似标签调度的东西来实现(对于前两个条件 - 第三个是真正的运行时检查(,以避免实例化代码在 taken 分支之外。
然后,错误是由此行引起的,该行在value_type
上使用std::move_if_noexcept
。故事长话短说来了。
value_type
std::pair<const std::string, y_u_no_elision>
.
在初始代码中:
-
holder
具有非删除、非禁止复制和移动构造函数。 - 这意味着
y_u_no_elision
的相应隐式声明构造函数也将不被删除且不例外。 - 这些特征传播到
value_type
的构造函数。 - 这会导致
std::move_if_noexcept
返回const value_type&
而不是value_type&&
(如果可以,它会回退到复制 - 请参阅这些文档(。 - 这最终会导致调用
y_u_no_elision
的复制构造函数,这将导致holder<ptrwrap>
的复制构造函数定义被实例化,从而尝试复制std::unique_ptr
。
现在,如果删除用户声明的复制并从holder
中移动构造函数和赋值运算符:
-
holder
将获得隐式声明的。复制构造函数将被删除,移动构造函数将是默认的,而不是删除和noexcept
。 - 这传播到
value_type
,除了一个与holder
无关的例外:value_type
的移动构造函数将尝试从const std::string
移动;这不会调用string
的移动构造函数(在这种情况下noexcept
(,而是调用它的复制构造函数,因为string&&
不能绑定到类型const string
的右值。 -
string
的复制构造函数不是noexcept
的(它可能必须分配内存(,所以value_type
的移动构造函数也不会。 - 那么,为什么要编译代码呢?因为
std::move_if_noexcept
背后的逻辑:即使参数的移动构造函数不是noexcept
,它也会返回一个右值引用,只要参数不是可复制的(如果它不能回退到复制,它会回退到非 noexcept move(;value_type
不是,因为holder's
删除了复制构造函数。
这是上述快速修复背后的逻辑:您必须做一些事情来使value_type
具有有效的移动构造函数和已删除的复制构造函数,以便从move_if_noexcept
获取右值引用。这是因为如上所述,由于const std::string
,您将无法使value_type
具有noexcept
移动构造函数。
holder
复制构造函数调用t
复制构造函数:
holder(const holder& b)
: t(b.t)
{
}
如果t
是unique_ptr
则不支持通过复制进行构造。
- 存储在 std::map/std::set 中,与在存储所有数据后对向量进行排序
- 如何在<N>不发生内存泄漏的情况下同时(线程安全)填充 c++11 std::map<std::string,std::bitset*>?
- 无法在 std::map<std::string,std::shared_ptr 中设置值<class>>
- 如何在C++中迭代集合映射(std::map<std::set< char>, int >)?
- 如何初始化结构字段 std::map<std::string, std::string>称为参数
- issue with std::map std::find
- 为什么 std::map< std::map >不释放内存?
- C++ map<std::string> vs map<char *> 性能(我知道,"again?" )
- 确定运行时std::map/std::set的内存使用情况
- 映射上的模板参数无效 std::map< std::string, Stock*> &Stock
- 我的SFINAE检查std::map/std::vector有什么问题
- 使用 std::map<std::string, int> 计算表达式树
- 将数据从两种不同的数据结构插入 std::map <std::string, int> mymap 并通过套接字发送
- 类中的编译器错误,数据类型为 typedef map<std::string,std::p air<std::string,vector<int>>> MapPai
- C++ std::map<std::string, std::set<std::string>> .如何循环设置值?
- 如何填写和访问 std::map<std::p air<enum1, enum2>, funcPtr>?
- 二进制'<':找不到 map<std::string shared_ptr 的运算符<Foo>>
- 如何将 2 个字符* 数组直接映射到 std::map<std::string,std::string>
- C++ std::map<std::string, int> 获取键以特定字符串开头的值
- 从取消引用的迭代器返回 std::map<std::string, int> 时出现巨大的内存泄漏