std::map的键出现问题

3
考虑以下代码。将由整数和整数向量组成的元组定义为映射的键。然而,当插入或查找一个由整数和整数组成的元组作为键时,编译器没有抛出任何错误,这让我感到惊讶。由于元组的第二个元素应该是整数向量类型,那么这是怎么回事呢?
std::map <boost::tuple<int, vector<int > >, int> test;
std::map <boost::tuple<int, vector<int > >, int>::iterator test_it;

vector <int> t;
t.push_back(4);

test.insert(make_pair(boost::make_tuple(3, t), 4));

test.insert(make_pair(boost::make_tuple(3, 6), 4));

test_it = test.find(boost::make_tuple(3, 7)); 
if(test_it != test.end()) 
throw " test is passed";  

1
你的代码无法编译,因为倒数第三行包含了"tes"而不是"test"。你能发一下你正在编译的实际代码吗? - Martin B
你能否添加必要的头文件并将其变成可编译的示例?我无法编译我的版本,所以我想确保我们尝试的是同一件事。另外,你使用的编译器和boost版本是什么? - juanchopanza
2个回答

1

似乎是 Boost 和许多 C++ 标准库实现中的一个 bug。这个问题同时影响了 pairtuple。最简单的演示代码如下:

#include <vector>
#include <utility>
using namespace std;
int main() {
    //compiles
    pair<int,vector<int>> bug1( pair<int,int>(5,6) );

    //compiles
    pair<int,vector<int>> bug2;
    bug2 = pair<int,int>(5,6);
}

Clang 4.0使用libc++编译,另一个也接受它,Comeau Online也接受。GCC 4.7.1会报错。

根据要求,它不应该被编译。

20.3.2/12

template<class U, class V> pair(const pair<U, V>& p);

Remark: This constructor shall not participate in overload resolution unless const U& is implicitly convertible to first_type and const V& is implicitly convertible to second_type.

20.3.2/23

template<class U, class V> pair& operator=(const pair<U, V>& p);

Requires: is_assignable<first_type&, const U&>::value is true and is_assignable<second_type&, const V&>::value is true.


你仍然不需要operator=引号,因为它从未被使用。 :) - Xeo
关键条件是隐式可转换的。有一个explicit转换std::vector<T>::vector(size_t, T = T()) - MSalters
这是我在回复中遗漏的措辞。(而且由于某种原因,尽管现在显示你一个小时前回答了这个问题,但在5分钟前我还看不到你的帖子。)这是来自C++11的,然而,在C++03下,他的代码必须编译。 - James Kanze
@sam 根据JamesKanze的说法,它在C++03中是合法的,所以很可能会有这种情况。但是你不应该再使用它了,因为在C++11中它现在是非法的,并且很可能很快就会在Boost中得到修复。 - hamstergene

1
问题在于隐式转换。它不是从intstd::vector<int>的转换;那样是行不通的,因为涉及的构造函数被声明为explicit,所以不能用于隐式转换。隐式转换是从std::pair<int, int>std::pair<int, std::vector<int>>。这使用了从模板派生的构造函数:template <typename U1, typename U2> std::pair(std::pair<U1, U2> const&),它不是隐式的。而这个构造函数的定义是:

template <typename T1, typename T2>
template <typename U1, typename U2>
std::pair<T1, T2>::std::pair( std::pair<U1, U2> const& other )
    : first( other.first )
    , second( other.second )
{
}

(这不完全是标准规定的方式。但是在C++03规范中,没有太多其他选择。在C++11中,有很多额外的负担,以便在可能的情况下进行移动构造,但我认为最终效果是相同的。)

请注意,在此构造函数中,存在显式调用构造函数,而不是隐式转换。因此,要使pair的隐式转换起作用,两种类型的显式转换就足够了。

个人而言,我怀疑这不是最初的意图。实际上,我怀疑大部分与std::pair相关的语言都是在添加explicit到语言之前冻结的,所以没有问题。后来,没有人想重新审视这个问题。在C++11中,重新审视它会破坏向后兼容性。因此,您会得到一些意外的转换。

请注意,这不是唯一的情况,其中转发导致显式转换变为隐式转换。考虑:

std::vector<std::vector<int> > v2D( 5, 10 );

显然,10不是一个std::vector<int>(第二个参数应该是这样的)。但是...在C++03中,这与构造函数模板匹配。
template<typename ForwardIterator, typename ForwardIterator>
std::vector( ForwardIterator begin, ForwardIterator end );

而标准对此有一些特殊的语言:

—— 构造函数

template <class InputIterator>
X(InputIterator f, InputIterator l, const Allocator& a = Allocator())

应具有相同的效力:

X(static_cast<typename X::size_type>(f),
static_cast<typename X::value_type>(l), a)

如果InputIterator是一个整数类型。

那么隐式转换已经变成了显式转换。

(请注意,没有这种特殊的语言,

std::vector<int> v(10, 42);

编译失败:上面模板构造函数的实例化是一个精确匹配,比 std::vector<int>( size_t, int ) 更好。委员会认为要求用户将上面的第一个整数显式转换为 size_t 可能有些过分了。

C++11 在这里进行了重大改变,并且:

std::vector<int, std::vector<int>> v2D( 10, 42 );

已不再合法。

至少我所看到的,std::pair 的构造函数没有应用任何此类更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接