How does the compiler distinguish the two options "vector :: insert"?

I am implementing a simple std::vector . There are two insert functions:

 template <typename T, typename Allocator> typename Vector<T, Allocator>::iterator Vector<T, Allocator>::insert(const_iterator pos, size_type count, const T& value) { checkIterator(pos); auto p = const_cast<iterator>(pos); if (count == 0) { return p; } for (size_type i = 0; i < count; ++i) { p = insert(p, value); } return p; } template <typename T, typename Allocator> template <typename InputIt> typename Vector<T, Allocator>::iterator Vector<T, Allocator>::insert(const_iterator pos, InputIt first, InputIt last) { checkIterator(pos); auto p = const_cast<iterator>(pos); if (first == last) { return p; } for (auto iter = first; iter != last; ++iter) { p = insert(p, *iter); ++p; } return p - (last-first); } 

But when I want to use the first insert function, the compiler calls the second:

 Vector<int> vi = {1, 2, 3}; vi.insert(vi.begin(), 3, 4); // get compile error, using insert(const_iterator pos, InputIt first, InputIt last). 

Why does the compiler choose the second function and how to modify my code to do it right?

+7
c ++ c ++ 11 stl
source share
2 answers

Unfortunately, doing this completely right is a problem. However, you can do something reasonable (and will work in this case). Basically, you need to conditionally enable the second overload depending on whether the output type InputIt meets the requirements for inputting an iterator. There is a whole list of requirements for the input iterator: http://en.cppreference.com/w/cpp/concept/InputIterator . However, we will simply focus on resolving this situation and the most common cases for us. Namely, we will check whether the InputIt type InputIt correct operator* . We use the void_t trick to create a trait for this:

 template <class ... T> using void_t = void; template <class T, class = void> struct has_iterator_deref : std::false_type {}; template <class T> struct has_iterator_deref<T, std::enable_if_t< std::is_same<typename std::iterator_traits<T>::reference, decltype(*std::declval<T>())>::value>> : std::true_type {}; 

Long and short is that this structure ensures that an instance of T can be dereferenced with * and will be of the same type as iterator_traits<T>::reference . Having done this, we now use this to hide the second overload:

 template <typename T, typename Allocator> template <typename InputIt, class = enable_if_t<has_iterator_deref<T>::value>> typename Vector<T, Allocator>::iterator Vector<T, Allocator>::insert(const_iterator pos, InputIt first, InputIt last) ... 

If you feel excited, you can actually go through the entire list of requirements for the input iterator, and as far as I can see, create a tag that determines if each one is present, and then finally take the connection with the correct detection to ensure Compliance of InputIt with the concept of Input Iterator. It is rather a pain, but.

+7
source share

A pretty simple way to do this is to rely on iterator_traits::iterator_category from <iterator>

It might look something like this:

 #include <iterator> template<typename It> using iterator_category_t = typename std::iterator_traits<It>::iterator_category; template<typename T> class container { public: template <typename InputIt, typename ItCat = iterator_category_t<InputIt>> iterator insert(const_iterator pos, InputIt first, InputIt last); // the rest 

This will lead to the disappearance of things like int , since they do not correspond to the category of iterators.

This has the added benefit that you can send to ItCat{} inside the implementation if you have different algorithms for different categorys iterators.

+1
source share

All Articles