Проблема с ключом std::map

Рассмотрим следующий код. Кортеж, состоящий из целого и вектора целого, определяется как ключ карты. Тем не менее, я был удивлен, что компилятор не выдает никакой ошибки при вставке или поиске кортежа, состоящего из целого числа и целого числа в качестве ключа. Как это может быть, поскольку второй элемент кортежа должен иметь тип вектора целое число?

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";  

2 ответа

Решение

Похоже, ошибка в Boost и многих реализациях стандартной библиотеки C++. Проблема разделяется обоими pair а также tuple, Простейший код, демонстрирующий это:

#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);
}

Лязг 4.0 с libc++ и другой принимает это, Comeau Online принимает это тоже. GCC 4.7.1 выдает ошибку.

Он не должен компилироваться, согласно:

20.3.2 / 12

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

Примечание. Этот конструктор не должен участвовать в разрешении перегрузки, если только const U& неявно преобразуется в first_type, а const V& неявно преобразуется в second_type.

20.3.2 / 23

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

Требуется: is_assignable<first_type&, const U&>::value является true а также is_assignable<second_type&, const V&>::value является true,

Проблема заключается в неявном преобразовании. Это не из int вstd::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,

Другие вопросы по тегам