выбрать конструктор в переполнении стека

Я практикую C ++, создавая свою собственную версию вектора, названную «Вектор». У меня есть два конструктора: конструктор заполнения и конструктор диапазона. Их заявления выглядят следующим образом:

template <typename Type>
class Vector {
public:
// fill constructor
Vector(size_t num, const Type& cont);

// range constructor
template<typename InputIterator> Vector(InputIterator first, InputIterator last);

/*
other members
......
*/
}

Конструктор заполнения заполняет контейнер числом val; и конструктор диапазона копирует каждое значение в диапазоне [first, last) в контейнер. Предполагается, что они совпадают с двумя конструкторами вектора STL.

Их определения идут следующим образом:

//fill constructor
template <typename Type>
Vector<Type>::Vector(size_t num, const Type& cont){
content = new Type[num];
for (int i = 0; i < num; i ++)
content[i] = cont;
contentSize = contentCapacity = num;
}

// range constructor
template <typename Type>
template<typename InputIterator>
Vector<Type>::Vector(InputIterator first, InputIterator last){
this->content = new Type[last - first];

int i = 0;
for (InputIterator iitr = first; iitr != last; iitr ++, i ++)
*(content + i) = *iitr;

this->contentSize = this->contentCapacity = i;
}

Однако, когда я пытаюсь их использовать, у меня возникают проблемы с их различением.
Например:

Vector<int> v1(3, 5);

С помощью этой строки кода я намеревался создать вектор, который содержит три элемента, каждый из которых равен 5. Но компилятор использует конструктор диапазона, рассматривая и «3», и «5» как экземпляры «InputIterator», что, без сюрпризов, вызывает ошибку.

Конечно, если я изменю код на:

Vector<int> v1(size_t(3), 5);

Все хорошо, конструктор заливки вызывается. Но это явно не интуитивно понятно и удобно для пользователя.

Итак, есть ли способ, которым я могу использовать конструктор заполнения интуитивно?

13

Решение

Ты можешь использовать std::enable_if (или же boost::enable_if если вы не используете C ++ 11) для устранения неоднозначности конструкторов.

#include <iostream>
#include <type_traits>
#include <vector>
using namespace std;template <typename Type>
class Vector {
public:
// fill constructor
Vector(size_t num, const Type& cont)
{
cout << "Fill constructor" << endl;
}

// range constructor
template<typename InputIterator> Vector(InputIterator first, InputIterator last,
typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
{
cout << "Range constructor" << endl;
}

};

int main()
{
Vector<int> v1(3, 5);
std::vector<int> v2(3, 5);
Vector<int> v3(v2.begin(), v2.end());
}

Приведенная выше программа должна сначала вызвать конструктор заполнения, проверив, является ли тип целочисленным типом (и, следовательно, не итератором.)

Кстати, в вашей реализации конструктора диапазона вы должны использовать std::distance(first, last) скорее, чем last - first, Явно используя - оператор на итераторах ограничивает вас RandomAccessIterator типы, но вы хотите поддержать InputIterator который является наиболее общим типом Итератора.

12

Другие решения

Четное std::vector кажется, есть эта проблема.

std::vector<int> v2(2,3);

выбирает

template<class _Iter>
vector(_Iter _First, _Iter _Last)

В Visual C ++, хотя он должен соответствовать ближе к шаблону без шаблонов.

Редактировать: Эта функция выше (правильно) делегирует конструкцию ниже. Я полностью потерян ..

template<class _Iter>
void _Construct(_Iter _Count, _Iter _Val, _Int_iterator_tag)

Edit # 2 AH !:

Каким-то образом эта ниже функция определяет, какая версия конструктора предназначена для вызова.

template<class _Iter> inline
typename iterator_traits<_Iter>::iterator_category
_Iter_cat(const _Iter&)
{   // return category from iterator argument
typename iterator_traits<_Iter>::iterator_category _Cat;
return (_Cat);
}

Выше показано _Construct Функция имеет (по крайней мере) 2 версии, перегружающие третью переменную, которая является тегом, возвращаемым вышеуказанным _Iter_cat функция. В зависимости от типа этой категории правильная перегрузка _Construct выбран

Окончательное редактирование:
iterator_traits<_Iter> является классом, который, по-видимому, шаблонируется для многих различных общих разновидностей, каждый из которых возвращает соответствующий тип «Category»

Решение: Похоже, шаблонная специализация типа первого аргумента состоит в том, как std Библиотека справляется с этой беспорядочной ситуацией (тип примитивного значения) в случае MS VC ++. Возможно, вы могли бы изучить это и последовать их примеру?

Проблема возникает (я думаю), потому что с примитивными типами значений Type а также size_t переменные похожи, и поэтому выбирается версия шаблона с двумя одинаковыми типами.

3

Проблема та же, что и у стандартной библиотеки. Есть несколько способов ее решить.

  • Вы можете тщательно предоставить не шаблонные перегруженные конструкторы для всех целочисленных типов (вместо первого параметра).

  • Вы можете использовать технику на основе SFINAE (например, enable_if), чтобы убедиться, что конструктор диапазона не выбран для целочисленного аргумента.

  • Вы можете разветвить конструктор диапазона во время выполнения (используя if) после обнаружения интегрального аргумента (с помощью is_integral) и перенаправить управление на правильный код конструирования. Условие ветвления будет значением времени компиляции, а это означает, что код, вероятно, будет сокращен компилятором во время компиляции.

  • Вы можете просто заглянуть в свою версию реализации стандартной библиотеки и посмотреть, как они это делают (хотя их подход не обязательно должен быть переносимым и / или действительным с точки зрения абстрактного языка C ++).

3

Эта двусмысленность вызвала проблемы у первых разработчиков библиотек. Это называется эффектом «Делай как надо». Насколько я знаю, вам нужно SFINAE чтобы решить это … это могло быть одним из первых применений этой техники. (Некоторые компиляторы обманывали и взламывали свои внутренние компоненты разрешения перегрузки, пока решение не было найдено в основном языке.)

Стандартная спецификация этой проблемы — одно из ключевых отличий между C ++ 98 и C ++ 03. Из C ++ 11, §23.2.3:

14 Для каждого контейнера последовательности, определенного в этом пункте и в пункте 21:

— Если конструктор

       template <class InputIterator>
X(InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type())

вызывается с типом InputIterator, который не квалифицируется как входной итератор, тогда конструктор не должен участвовать в разрешении перегрузки.

15 Степень, в которой реализация определяет, что тип не может быть входным итератором, не определена, за исключением того, что как минимум целочисленные типы не должны квалифицироваться как входные итераторы.

2
По вопросам рекламы [email protected]