我不明白为什么这段代码会编译:
#include <set>
#include <list>
#include <algorithm>
int modify(int i)
{
return 2*i;
}
int main (int args, char** argv)
{
std::set<int> a;
a.insert(1);
a.insert(2);
a.insert(3);
std::list<int> b; // change to set here
std::transform(a.begin(), a.end(), b.begin(), modify); // line 19
}
虽然,如果我只是将b的类型从std::list<int>
更改为std::set<int>
,它会在编译时(第19行)失败,并显示错误:只读变量不可分配< / em>的。
要将b用作集合,我需要将变换行更改为
std::transform(a.begin(), a.end(), std::inserter(b, b.begin()), modify);
为什么?我不知何故猜测原因与set是一个关联容器这一事实有关,而list是一个序列容器,但我可能完全不在这里。
我忘了提到:我在gcc 3.4.2和llvm 3.3上使用默认标准(c ++ 98)尝试过这个。我使用c ++ 03在llvm 3.3上再次尝试,我得到了相同的行为。
答案 0 :(得分:4)
在没有std::inserter
的代码中,transform
分配给*b.begin()
。在set
的情况下,它是对元素的const引用(自C ++ 11以来)。因此,编译时错误。
对于list
,它仍然分配给*b.begin()
,它编译但行为未定义,因为列表的大小为0.因此b.begin()
可能无法解除引用。
这是正确的,这与set
是一个关联容器这一事实有关,而list
是一个序列。关联容器不允许您修改用作键的元素部分。如果set
部分是元素,则对于map
,您可以修改值,但不能修改密钥。
std::inserter
的重点是安排而不是通过迭代器分配,而是调用insert
。
答案 1 :(得分:3)
首先,您拥有的代码表现出未定义的行为,因为目标列表实际上没有空间。使用back_inserter
可以随时创建空间。
对于集合,集合的元素是不可变的。这就是为什么你不能分配给一个解除引用的迭代器,即使你有空间。但使用inserter
完全没问题。
答案 2 :(得分:1)
此记录
std::transform(a.begin(), a.end(), b.begin(), modify);
对std::list<int>
无效(尽管它可以在C ++ 2003中为std :: list或std :: set编译,其中set具有非const迭代器),因为你定义了一个空列表。
std::list<int> b;
当使用这样的记录时,假设输出容器已经具有
范围内的元素[b.begin(), b.begin() + distance( a.begin(), a.end() ) )
因为他们被重新分配了。 因此,如果考虑一个集合,那么假设该集合已经具有所有必需元素,但您可能不会更改它们。当您使用iterator adapter std :: insert_iterator时,它会在容器中添加新元素。所以在这种情况下你可以使用一套。
答案 3 :(得分:1)
在C ++ 03中,此代码编译但导致未定义的行为 - 您不能简单地更改set的值,因为它们必须按升序排列。在已修复的c ++ 11中,set::iterator
是const T
上的双向迭代器,因此您根本无法更改其值。 std::inserter
不会更改现有值,而是会在operator++
中插入新值,因此一切正常