Search code examples
c++vectoriteratorc++17assign

Own vector assign implementation


I'm implementing stl like vector with writing all default functions. And there is a problem that I don't understand why It calls ragne version of assign for simple types and doesn't default. Here is the implementation code: Vector.h

void assign(size_t count, const T& value){ // Default version
                void assign(size_t count, const T& value){
                if(this->_size < count){
                   this->allocator.deallocate(this->arr, this->_capacity);
                   this->arr = this->allocator.allocate(count);

                   this->_capacity = count;
                }
                for(size_t i = 0; i < count; ++i)
                    this->arr[i] = value;

               this->_size = count;
            }

template<class InputIt>
            void assign(InputIt first, InputIt last){ // Range version
                size_t count = std::distance(first,last);
                if(this->_size < count){
                   this->allocator.deallocate(this->arr, this->_capacity);
                   this->arr = this->allocator.allocate(count);

                   this->_capacity = count;
                }
                for(size_t i = 0; first != last; i++)
                    this->arr[i] = *first++;

                this->_size = count;
            }

Main code:

 Vector<int> vec;
 vec.assign(5,10);

Output:


/MyVector/MyVector.h: In instantiation of ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’:
../MyVector/main.cpp:52:24:   required from here
../MyVector/MyVector.h:99:45: error: no matching function for call to ‘distance(int&, int&)’
                 size_t count = std::distance(first,last);
                                ~~~~~~~~~~~~~^~~~~~~~~~~~
In file included from /usr/include/c++/7/bits/stl_algobase.h:66:0,
                 from /usr/include/c++/7/bits/char_traits.h:39,
                 from /usr/include/c++/7/ios:40,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from ../MyVector/main.cpp:1:
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: note: candidate: template<class _InputIterator> constexpr typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator)
     distance(_InputIterator __first, _InputIterator __last)
     ^~~~~~~~
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: note:   template argument deduction/substitution failed:
/usr/include/c++/7/bits/stl_iterator_base_funcs.h: In substitution of ‘template<class _InputIterator> constexpr typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator) [with _InputIterator = int]’:
../MyVector/MyVector.h:99:45:   required from ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’
../MyVector/main.cpp:52:24:   required from here
/usr/include/c++/7/bits/stl_iterator_base_funcs.h:138:5: error: no type named ‘difference_type’ in ‘struct std::iterator_traits<int>’
In file included from ../MyVector/main.cpp:2:0:
../MyVector/MyVector.h: In instantiation of ‘void Vector<T, Allocator>::assign(InputIt, InputIt) [with InputIt = int; T = int; Allocator = std::allocator]’:
../MyVector/main.cpp:52:24:   required from here
../MyVector/MyVector.h:107:36: error: invalid type argument of unary ‘*’ (have ‘int’)
                     this->arr[i] = *first++;
                                    ^~~~~~~~
Makefile:725: recipe for target 'main.o' failed
make: *** [main.o] Error 1

I'm using C++17


Solution

  • The range version is a better match for vec.assign(5, 10); with InputIt = int. You should somehow disable that overload for a template parameter that doesn't represent an input iterator.

    Let's take a look at stdlibc++ implementation:

    template<typename InputIt, typename = std::RequireInputIter<InputIt>>
    void assign(InputIt first, InputIt last) {
        M_assign_dispatch(first, last);
    }
    

    where RequireInputIter is

    template<typename InputIt>
    using RequireInputIter = typename enable_if<is_convertible<typename
        iterator_traits<InputIt>::iterator_category, input_iterator_tag>::value>::type;
    

    In other words, for a deduced type InputIt, iterator_traits<InputIt>::iterator_category type should be convertible into input_iterator_tag. Otherwise, that assign overload is silently excluded from the overload resolution set thanks to SFINAE.

    In C++17, RequireInputIter can be simplified with _t and _v helpers:

    template<typename InputIt>
    using RequireInputIter = enable_if_t<is_convertible_v<typename
        iterator_traits<InputIt>::iterator_category, input_iterator_tag>>;
    

    Also note that input iterators can be used to traverse a range only once. After you call std::distance(first, last), all subsequent attempts to traverse the range are undefined behaviour unless InputIt is at least a forward iterator. For input iterators you can't determine how much space to preallocate.

    That's why assign uses tag dispatch technique internally. With some simplifications it looks like this:

    template<typename InputIt, typename = std::RequireInputIter<InputIt>>
    void assign(InputIt first, InputIt last) {
        M_assign_aux(first, last, 
            typename iterator_traits<InputIt>::iterator_category{});
    }
    

    There are two M_assign_aux overloads

    template<typename InputIt>
    void M_assign_aux(InputIt first, InputIt last, std::input_iterator_tag);
    
    template<typename ForwardIt>
    void M_assign_aux(ForwardIt first, ForwardIt last, std::forward_iterator_tag);
    

    to do the assignment. The first one will be used for input iterators only, and the second one - for forward iterators and those derived from it, i.e. bidirectional and random access ones.