How to make constructor accept all type of iterators?

好久不见. 提交于 2019-12-31 04:04:29

问题


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



来源:https://stackoverflow.com/questions/58867411/how-to-make-constructor-accept-all-type-of-iterators

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!