Я практикую 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);
Все хорошо, конструктор заливки вызывается. Но это явно не интуитивно понятно и удобно для пользователя.
Итак, есть ли способ, которым я могу использовать конструктор заполнения интуитивно?
Ты можешь использовать 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
который является наиболее общим типом Итератора.
Четное 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
переменные похожи, и поэтому выбирается версия шаблона с двумя одинаковыми типами.
Проблема та же, что и у стандартной библиотеки. Есть несколько способов ее решить.
Вы можете тщательно предоставить не шаблонные перегруженные конструкторы для всех целочисленных типов (вместо первого параметра).
Вы можете использовать технику на основе SFINAE (например, enable_if
), чтобы убедиться, что конструктор диапазона не выбран для целочисленного аргумента.
Вы можете разветвить конструктор диапазона во время выполнения (используя if
) после обнаружения интегрального аргумента (с помощью is_integral
) и перенаправить управление на правильный код конструирования. Условие ветвления будет значением времени компиляции, а это означает, что код, вероятно, будет сокращен компилятором во время компиляции.
Вы можете просто заглянуть в свою версию реализации стандартной библиотеки и посмотреть, как они это делают (хотя их подход не обязательно должен быть переносимым и / или действительным с точки зрения абстрактного языка C ++).
Эта двусмысленность вызвала проблемы у первых разработчиков библиотек. Это называется эффектом «Делай как надо». Насколько я знаю, вам нужно 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 Степень, в которой реализация определяет, что тип не может быть входным итератором, не определена, за исключением того, что как минимум целочисленные типы не должны квалифицироваться как входные итераторы.