Проблема с ключом 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
,