How to make constructor accept all type of iterato

2020-02-07 07:36发布

问题:

I am creating a custom Vector/ArrayList class. But i´m having troubles creating the iterative version of the constructor. The following code works, but the problem is when i want to create ArrayList like this:

ArrayList arr(1, 5);

The compiler doesn't know what version of the function should pick.

How can I solve this?

The constructors:

ArrayList(const size_type elem_amount, value_type elem) : arr_size { elem_amount }, arr_capacity{ elem_amount }
{
    array = std::uninitialized_fill_n(allocator.allocate(arr_size), arr_size, elem) - arr_size;
    first = array;
    last = array + arr_size - 1;
}


template<typename ITER>
ArrayList(ITER begin, ITER end) : arr_size{ static_cast<size_type>(end - begin) }, arr_capacity{ arr_size }
{
    std::uninitialized_copy(begin, end, array = allocator.allocate(arr_size));
    first = array;
    last = array + arr_size - 1;
}

回答1:

What you need to do is use SFINAE to constrain the template to only work when the template type is deduced to be an iterator type. Since you do arr_size{ static_cast<size_type>(end - begin) } to initialize size this means that you expect the iterators to be random access. We can check for that using the iterator_category of std::iterator_traits Doing that gives you

template<typename ITER, 
         std::enable_if_t<std::is_base_of_v<typename std::iterator_traits<ITER>::iterator_category, 
                                            std::random_access_iterator_tag>, bool> = true>
ArrayList(ITER begin, ITER end) : arr_size{ static_cast<size_type>(end - begin) }, arr_capacity{ arr_size }
{
    std::uninitialized_copy(begin, end, array = allocator.allocate(arr_size));
    first = array;
    last = array + arr_size - 1;
}


回答2:

With C++20 you can use concepts

Note that some compilers start supporting the new features of C++20, including concepts: https://en.cppreference.com/w/cpp/compiler_support

So to differentiate between your two constructors, with C++20 you restrict the allowed template arguments using a concept type:

template<std::random_access_iterator ITER>
         // see comment by Nathan Oliver for using random_access_iterator 
ArrayList(ITER begin, ITER end)
: arr_size{ static_cast<size_type>(end - begin) }, arr_capacity{ arr_size }
{
    std::uninitialized_copy(begin, end, array = allocator.allocate(arr_size));
    first = array;
    last = array + arr_size - 1;
}

If you wish to define your own concept (no need to in this case, just for the sake of the exercise), you can define a concept for Iterator or InputIterator:

template<typename ITER>
concept Iterator = requires {
    typename std::iterator_traits<ITER>::iterator_category;
};

template<typename ITER>
concept RandomAccessIterator = std::is_base_of_v <
    std::random_access_iterator_tag,
    typename std::iterator_traits<ITER>::iterator_category
>;

And use it the same as above, for example:

template<RandomAccessIterator ITER>
ArrayList(ITER begin, ITER end)  // ...

Code: https://godbolt.org/z/gBevvC

(Note that RandomAccessIterator should be part of ranges, but can be implemented on your own as above).