Подтвердить что ты не робот

Выберите, какой конструктор в С++

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

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; и конструктор диапазона копирует каждое значение в диапазоне [первый, последний] в контейнер. Они должны быть одинаковыми с двумя конструкторами вектора 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);

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

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

4b9b3361

Ответ 1

Вы можете использовать std::enable_if (или boost::enable_if, если вы не используете С++ 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, который является наиболее общим типом Iterator.

Ответ 2

Даже std::vector, похоже, эта проблема.

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

выбирает

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

В Visual С++, хотя он должен соответствовать ближе к не templated случае..

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

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

Изменить # 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> - это класс, который, по-видимому, предназначен для многих разных общих разновидностей, каждый из которых возвращает соответствующий тип категории

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

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

Ответ 3

Проблема такая же, как и в случае стандартной реализации библиотеки. Существует несколько способов его решения.

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

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

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

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

Ответ 4

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

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

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

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

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

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

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