【问题标题】:How to make constructor accept all type of iterators?如何让构造函数接受所有类型的迭代器?
【发布时间】:2020-03-11 00:56:44
【问题描述】:

我正在创建一个自定义 Vector/ArrayList 类。但是我在创建构造函数的迭代版本时遇到了麻烦。以下代码有效,但问题是当我想像这样创建 ArrayList 时:

ArrayList arr(1, 5);

编译器不知道应该选择哪个版本的函数。

我该如何解决这个问题?

构造函数:

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;
}

【问题讨论】:

    标签: c++ class templates iterator


    【解决方案1】:

    使用 C++20,您可以使用概念

    请注意,一些编译器开始支持 C++20 的新特性,包括概念https://en.cppreference.com/w/cpp/compiler_support

    因此,为了区分您的两个构造函数,在 C++20 中,您使用概念类型来限制允许的模板参数:

    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;
    }
    

    如果您想定义自己的概念(在这种情况下不需要,只是为了练习),您可以为 Iterator 定义一个概念:

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

    或者,在这种情况下,使用 RandomAccessIterator:

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

    并使用同上,例如:

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

    代码:https://godbolt.org/z/gBevvC

    (请注意,RandomAccessIterator 应该是范围的一部分,但可以像上面一样自行实现)。

    【讨论】:

    • 请注意input_iterator 的合约太弱了。做arr_size{ static_cast&lt;size_type&gt;(end - begin) } 至少需要随机访问迭代器。
    • @NathanOliver - 确实如此。固定。
    【解决方案2】:

    您需要做的是使用SFINAE 将模板限制为仅在模板类型被推断为迭代器类型时才起作用。由于您使用arr_size{ static_cast&lt;size_type&gt;(end - begin) } 来初始化size,这意味着您希望迭代器是随机访问的。我们可以使用std::iterator_traitsiterator_category 进行检查,这样做会给你

    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;
    }
    

    【讨论】:

    • 我会使用std::is_base_of_v(参数顺序颠倒)而不是is_same_v。这使得更改为不太具体的类型变得更加容易,并且可以针对 C++20 std::contiguous_iterator_tag 进行未来验证。
    • @aschepler 好电话。就是这么想的。编辑它。
    猜你喜欢
    • 1970-01-01
    • 2014-09-18
    • 1970-01-01
    • 2020-12-26
    • 2016-05-24
    • 2013-09-11
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    相关资源
    最近更新 更多