liquidhand
liquidhand

Reputation: 33

Wrong constructor called in custom vector class

I'm fairly new to C++ and as a learning exercise I'm creating my own simple vector class.

It seems to work well, except when I create a Vec object trying to use the
Vec(size_type n, const T& val = T()) constructor, it instead wants to use the templated constructor that is supposed to take two iterators/pointers, and gives me errors.

I believe that I understand why this is happening, but I can't think of a way to make it less ambiguous without changing the way the class is used. I want to keep the use the same as with a std::vector.

How can I get the proper constructor called? How does the std::vector avoid this problem?

Vec.h

//#includes: <algorithm> <cstddef> <memory>

template <class T> class Vec
{
public:
//typedefs for iterator, size_type....
//just used T* for iterator

    Vec() { create(); }
    Vec(const Vec& v) { create(v.begin(), v.end()); }
    explicit Vec(size_type n, const T& val = T()) { create(n, val); }

    template <class InputIt>
    Vec(InputIt first, InputIt last) { create(first, last); }

    ~Vec() { uncreate(); }

    T& operator[](size_type i) { return data[i]; }
    const T& operator[](size_type i) const { return data[i]; }
    Vec& operator=(const Vec&);

    //functions such as begin(), size(), etc...

private:
    iterator data;
    iterator avail;
    iterator limit;

    std::allocator<T> alloc;
    void create();
    void create(size_type, const T&);

    template <class InputIt>
    void create(InputIt first, InputIt last)
    {
        data = alloc.allocate(last - first);
        limit = avail = std::uninitialized_copy(first, last, data);
    }

    void uncreate();
    void grow();
    void unchecked_append(const T&);
};

template <class T>
Vec<T>& Vec<T>::operator=(const Vec& rhs)
{
    if (&rhs != this)
    {
        uncreate();
        create(rhs.begin(), rhs.end());
    }
    return *this;
}

template <class T> void Vec<T>::create()
{
    data = avail = limit = 0;
}

template <class T> void Vec<T>::create(size_type n, const T& val)
{
    data = alloc.allocate(n);
    limit = avail = data + n;
    std::uninitialized_fill(data, limit, val);
}

template <class T>
void Vec<T>::create(const_iterator i, const_iterator j)
{
    data = alloc.allocate(j - i);
    limit = avail = std::uninitialized_copy(i, j, data);
}

template <class T> void Vec<T>::uncreate()
{
    if (data)
    {
        iterator it = avail;
        while (it != data)
            alloc.destroy(--it);
        alloc.deallocate(data, limit - data);
    }
    data = limit = avail = 0;
}

//left out what I didn't think was relevant

It seems to work fine except the case I mentioned above.

main.cpp

#include "Vec.h"

main()
{
    Vec<int> v1(10, 100);
}

I get errors:

||=== Build: Debug in Vec Class (compiler: GNU GCC Compiler) ===|
c:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\bits\stl_uninitialized.h||In instantiation of '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = int; _ForwardIterator = int*]':|
F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\Vec.h|58|required from 'void Vec::create(InputIt, InputIt) [with InputIt = int; T = int]'|
F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\Vec.h|24|required from 'Vec::Vec(InputIt, InputIt) [with InputIt = int; T = int]'|
F:\CBProjects\Accelerated C++\Programming with Classes\Vec Class\main.cpp|9|required from here|
c:\program files\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\bits\stl_uninitialized.h|113|error: no type named 'value_type' in 'struct std::iterator_traits'|
||=== Build failed: 1 error(s), 4 warning(s) (0 minute(s), 0 second(s)) ===|

This is my first time asking a question on here. I hope I asked properly and provided enough information. Please let me know if I made a mistake in this process, and thank you for any help you can provide.

EDIT:

For anyone wondering what I ended up doing. I changed the templated constructor so that it won't be invoked with arithmetic arguments(since it's only meant for iterators/pointers).

template <class InputIt>
Vec(InputIt first, InputIt last,
    typename std::enable_if<!std::is_arithmetic<InputIt>::value>::type* = 0) {create(first, last); }  

It seems to work fine, but I'm actually not very familiar with enable_if, so if anyone knows any dangers to this approach, please let me know.

Upvotes: 2

Views: 798

Answers (1)

pmr
pmr

Reputation: 59811

The templated constructor is chosen since it is a better match for the arguments.

Looking at the documentation of the vector constructor you will see a note that this constructor only participates in overload resolution if the arguments satisfy the requirements of InputIterator. This is usually achieved by disabling an overload using SFINAE. The following is how libcpp achieves that. I tried to cut away some unnecessary bits and adjusted the naming conventions to make for better readability.

A usual definition of this constructor in an actual implementation would look like this:

template <class InputIterator>
vector(InputIterator first, InputIterator last,
       typename enable_if<is_input_iterator  <InputIterator>::value>::type* = 0);
template <class InputIterator>
vector(InputIterator first, InputIterator last, const allocator_type& a,
       typename enable_if<is_input_iterator  <InputIterator>::value>::type* = 0);

I removed the unnecessary parts that further differentiates between InputIterator and ForwardIterator. The important part for SFINAE is the enable_if and its arguments.

Here are the necessary definitions of is_input_iterator:

template <class Tp, class Up,
          bool = has_iterator_category<iterator_traits<Tp> >::value>
struct has_iterator_category_convertible_to
    // either integral_constant<bool, false> or integral_constant<bool, true>
  : public integral_constant<
      bool, 
      is_convertible<typename iterator_traits<Tp>::iterator_category, Up>::value
    >
{};

// specialization for has_iterator_category<iterator_traits<Tp> >::value == false
template <class Tp, class Up>
struct has_iterator_category_convertible_to<Tp, Up, false> : public false_type {};

template <class Tp>
struct is_input_iterator 
  : public has_iterator_category_convertible_to<Tp, input_iterator_tag> {};

The essence of it is that it checks if a given type has an iterator_category tag that can be converted to input_iterator_tag.

has_iterator_category is even more obscure:

template <class Tp>
struct has_iterator_category
{
private:
    struct two {char lx; char lxx;};
    template <class Up> static two test(...);
    template <class Up> static char test(typename Up::iterator_category* = 0);
public:
    static const bool value = sizeof(test<Tp>(0)) == 1;
};

It is used to check if a given type has a member called iterator_category or not. If the answer is no has_iterator_category_convertible_to will simply default to false_type. If this wouldn't be done a non-exisiting name would be accessed and a hard compiler error would be triggered, which is not what you want to do in SFINAE.

Upvotes: 3

Related Questions