Why does this constructor overload resolve incorre

2019-05-03 09:45发布

This is my (stripped) class and instantiation of one object:

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);

I would expect this to call the Carray(size_type, const T&, const Allocator&) constructor, but it doesn't. Apparantly this resolutes to template<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&).

What should I change to make this work as intended? I find it weird also, because a call to std::vector<int> v(5, 10) works perfectly fine. And if I look at the definition of the constructors in my GCC's implementation I find this (I renamed some compiler-implementation names, like __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());

    // ...
};

which seems to be the same.

4条回答
太酷不给撩
2楼-- · 2019-05-03 10:07

This should work with all iterator types (including pointers) and the current standard.

#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));
}

Explanation, an iterator must normally define several types such as iterator_category, and you can take advantage of this and sfinae to detect real iterators. Complication is that pointers are also iterators, but don't have these types defined (something std::iterator_traits provides a specialization for), so the above takes a similar approach, if the passed in type is a pointer, then it is by default treated as an iterator. This approach saves you having to test for integral types.

See demo: http://www.ideone.com/E9l1T

查看更多
3楼-- · 2019-05-03 10:07

Try this. It will eliminate the iterator constructor from consideration if two ints are passed:

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

Reference: http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html


EDIT: responding to "Is there any way with just the C++03 STL and without boost?"

I don't know if std::type_traits is in C++03 or not -- I always have boost available, so I just use it. But you can try this. It will work in this specific case, but may not have the generality you require:

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";
  }
};
查看更多
欢心
4楼-- · 2019-05-03 10:16

The first constructor expects the 'value' argument to be passed by reference, while the second constructor expects the first 2 values to be passed by value. In my experience, C++ is quite strict about this distinction, try passing an integer variable instead of an integer value as the 2nd argument to your object constructor.

查看更多
家丑人穷心不美
5楼-- · 2019-05-03 10:17

The explicit constructor expects a size_t and an int. You have provided two ints.

Substituting int for InputIterator makes the template a better match.

If you look closer at the standard containers, you will see that they use some template meta-programming to determine if the InputIterator could be a real iterator or if it is an integer type. This then redirects to a different construction.

Edit
Here is one way of doing it:

  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()); }

You could also use boost::type_traits if the compiler doesn't have std::type_traits.

查看更多
登录 后发表回答