Почему перегрузка конструктора разрешается неправильно?

Это мой (раздетый) класс и реализация одного объекта:

template <typename T, typename Allocator = std::allocator<T> >
class Carray {
    typedef typename Allocator::size_type size_type;

    // ...

    explicit Carray(size_type n, const T& value, const Allocator& alloc = Allocator()) {
        // ...
    }

    template<typename InputIterator>
    Carray(InputIterator first, InputIterator last, const Allocator& alloc = Allocator()) {
        // ...
    }

    // ...
}

Carray<int> array(5, 10);

Я ожидаю, что это вызовет Carray(size_type, const T&, const Allocator&) конструктор, но это не так. Очевидно, это решает template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&),

Что я должен изменить, чтобы сделать эту работу как задумано? Я тоже нахожу это странным, потому что std::vector<int> v(5, 10) отлично работает И если я смотрю на определение конструкторов в моей реализации GCC, я нахожу это (я переименовал некоторые имена реализации компилятора, например: __n):

template<typename T, typename A = std::allocator<T> >
class vector {
    typedef size_t size_type;
    typedef T value_type;
    typedef A allocator_type;

    // ...

    explicit vector(size_type n, const value_type& value = value_type(), const allocator_type& a = allocator_type());

    template<typename InputIterator>
    vector(InputIterator first, InputIterator last, const allocator_type& a = allocator_type());

    // ...
};

что, кажется, то же самое.

4 ответа

Решение

Это должно работать со всеми типами итераторов (включая указатели) и текущим стандартом.

#include <iostream>
#include <iterator>
#include <vector>

// uses sfinae to determine if the passed in type is indeed an iterator
template <typename T>
struct is_iterator_impl
{
  typedef char yes[1];
  typedef char no[2];

  template <typename C>
  static yes& _test(typename C::iterator_category*);

  template <typename>
  static no& _test(...);

  static const bool value = sizeof(_test<T>(0)) == sizeof(yes);
};

template <typename T, bool check = is_iterator_impl<T>::value>
struct is_iterator
{
  typedef void type;
};

template <typename T>
struct is_iterator<T, false>
{
};

template <typename T>
struct is_iterator<T*, false>
{
  typedef void type;
};

template <typename T>
struct foo
{
  explicit foo(std::size_t n, const T& value) 
  {
    std::cout << "foo:size_t" << std::endl;
  }

  template<typename InputIterator>
  foo(InputIterator first, InputIterator last, typename is_iterator<InputIterator>::type* dummy = 0) 
  {
    std::cout << "foo::iterator" << std::endl;
  }
};

int main(void)
{
  // should not cause a problem
  foo<int> f(1, 2);

  // using iterators is okay
  typedef std::vector<int> vec;
  vec v;
  foo<int> b(v.begin(), v.end());

  // using raw pointers - is okay
  char bar[] = {'a', 'b', 'c'};
  foo<char> c(bar, bar + sizeof(bar));
}

Пояснение: итератор должен обычно определять несколько типов, таких как iterator_category, и вы можете воспользоваться этим и sfinae для обнаружения реальных итераторов. Сложность заключается в том, что указатели также являются итераторами, но эти типы не определены (что-то std::iterator_traits обеспечивает специализацию для), поэтому вышеприведенный подход использует аналогичный подход: если переданный тип является указателем, то по умолчанию он рассматривается как итератор. Такой подход избавляет вас от необходимости проверять целочисленные типы.

Посмотреть демо: http://www.ideone.com/E9l1T

Явный конструктор ожидает size_t и int. Вы предоставили два целых

Подставляя int за InputIterator делает шаблон лучше соответствовать.

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

редактировать
Вот один из способов сделать это:

  template<class _InputIterator>
  vector(_InputIterator _First, _InputIterator _Last,
         const allocator_type& _Allocator = allocator_type() )
     : _MyAllocator(_Allocator), _MyBuffer(nullptr), _MySize(0), _MyCapacity(0)
  { _Construct(_First, _Last, typename std::is_integral<_InputIterator>::type()); }

private:
  template<class _IntegralT>
  void _Construct(_IntegralT _Count, _IntegralT _Value, std::true_type /* is_integral */)
  { _ConstructByCount(static_cast<size_type>(_Count), _Value); }

  template<class _IteratorT>
  void _Construct(_IteratorT _First, _IteratorT _Last, std::false_type /* !is_integral */)
  { _Construct(_First, _Last, typename std::iterator_traits<_IteratorT>::iterator_category()); }

Вы также можете использовать boost::type_traits, если у компилятора нет std::type_traits.

Попробуй это. Это исключит конструктор итератора из рассмотрения, если будут переданы два целых числа:

template<typename InputIterator>
Carray(InputIterator first, InputIterator last,
    const Allocator& alloc = Allocator(),
    typename boost::disable_if<boost::is_integral<InputIterator> >::type* dummy = 0) {
}

Ссылка: http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html


РЕДАКТИРОВАТЬ: отвечая на "Есть ли способ только с C++03 STL и без повышения?"

Я не знаю, есть ли std::type_traits в C++ 03 или нет - у меня всегда есть boost, поэтому я просто использую его. Но вы можете попробовать это. Это будет работать в этом конкретном случае, но может не иметь необходимой вам общности:

template <class T> class NotInt { typedef void* type; };
template <> class NotInt<int> { };

template <typename T, typename Allocator = std::allocator<T> >
class Carray {
  ...
  template<typename InputIterator>
  Carray(InputIterator first, InputIterator last,
      const Allocator& alloc = Allocator(),
      typename NotInt<InputIterator>::type t = 0) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
  }
};

Первый конструктор ожидает, что аргумент 'value' будет передан по ссылке, а второй конструктор ожидает, что первые 2 значения будут переданы по значению. По моему опыту, C++ довольно строго относится к этому различию, попробуйте передать целочисленную переменную вместо целочисленного значения в качестве второго аргумента конструктору объекта.

Другие вопросы по тегам