Как вставить дублирующий элемент в вектор?

Я пытаюсь вставить копию существующего элемента vector, чтобы удвоить его. Следующий код работал в предыдущих версиях, но с ошибкой в ​​Visual Studio 2010.

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char* argv[])
{
   vector<int> test;
   test.push_back(1);
   test.push_back(2);
   test.insert(test.begin(), test[0]);
   cout << test[0] << " " << test[1] << " " << test[2] << endl;
   return 0;
}

Вывод -17891602 1 2, ожидаемый 1 1 2.

Я выяснил, почему это происходит - вектор перераспределяется, и ссылка становится недействительной, прежде чем она будет скопирована в точку вставки. Более старая Visual Studio, по-видимому, делала что-то в другом порядке, доказывая тем самым, что один из возможных результатов поведения undefined заключается в том, чтобы работать правильно, а также доказывая, что он никогда не должен полагаться на него.

Я придумал два разных способа решить эту проблему. Один из них - использовать reserve, чтобы убедиться, что перераспределение не происходит:

   test.reserve(test.size() + 1);
   test.insert(test.begin(), test[0]);

Другой - сделать копию из ссылки так, чтобы не было никакой зависимости от ссылки, остающейся действительной:

template<typename T>
T make_copy(const T & original)
{
    return original;
}

   test.insert(test.begin(), make_copy(test[0]));

Хотя оба работают, никто не чувствует себя естественным решением. Что-то мне не хватает?

Ответ 1

Проблема заключается в том, что vector::insert берет ссылку на значение как второй параметр, а не значение. Вам не нужен шаблон для создания копии, просто используйте конструктор копирования для создания другого объекта, который будет передаваться по ссылке. Эта копия остается действительной, даже если вектор изменен.

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char* argv[])
{
   vector<int> test;
   test.push_back(1);
   test.push_back(2);
   test.insert(test.begin(), int(test[0]));
   cout << test[0] << " " << test[1] << " " << test[2] << endl;
   return 0;
}

Ответ 2

Я считаю, что это определенное поведение. В §23.2.3 стандарта 2011 С++ в таблице 100 перечислены требования контейнера последовательности, и в этом случае есть запись. Он дает примерное выражение

a.insert(p,t)

где a - это значение X, которое является типом контейнера последовательности, содержащим элементы типа T, p является константным итератором с a, а T является значением lvalue или const rvalue типа X::value_type, т.е. T.

Утверждение для этого выражения:

Требуется: T должно быть CopyInsertable в X. Для vector и deque, T также должно быть CopyAssignable. Эффекты: Вставляет копию T до p.

Единственная соответствующая векторная конкретная цитата, которую я мог найти, находится в пункте §23.3.6.5:

Примечания: вызывает перераспределение, если новый размер больше старой. Если перераспределение не происходит, все итераторы и ссылки до точки вставки остаются в силе.

Хотя это и говорит о перераспределении вектора, он не делает исключение из предыдущих требований для insert в контейнерах последовательностей.

Что касается работы над этой проблемой, я согласен с предложением @EdChum просто сделать копию элемента и вставить эту копию.