【问题标题】:Create contiguous_iterator for custom class为自定义类创建 contiguous_iterator
【发布时间】:2021-12-21 16:31:45
【问题描述】:

总结

我有一个自定义数组类:

template<typename T, int SIZE>
class Array {
private:
    T mArray[SIZE];
};

为了支持带有范围的标准算法,我想为这个类创建一个迭代器。 std::contiguous_iterator 似乎是最佳选择,因为我可以保证数据的连续内存布局。在iterator tutorial 之后,我应该在这个类中创建一个类。但是,我应该以某种方式(引用)“例如,您可以用 std::forward_iterator 概念标记迭代器,而不是 std::forward_iterator_tag 标记。”

我很难弄清楚它的语法是什么样的,而且我一直无法在网络上找到展示这个的帖子。

问题

如何完成以下代码 sn-p 为我的Array&lt;T,S&gt; 类实现std::contiguous_iterator?:

import <iterator>;

template<typename T, int SIZE>
class Array {
public:
    const T& operator[](int i) { return mArray[i]; }
    T& operator[](int i) { return mArray[i]; }

private:
    T mArray[SIZE];

public:
    struct Iterator {
        Iterator(T* ptr) : mPtr(ptr) {}

    private:
        T* mPtr;
    };

    Iterator begin() { return Iterator(&mArray[0]); }
    Iterator end() { return Iterator(&mArray[SIZE]); }
};

注意:很多运算符重载。不需要提供所有答案。我只需要一个示例语法来说明该概念的放置位置,然后我可能会弄清楚其余部分。

【问题讨论】:

  • 不是答案,但有一个便宜的解决方案。指针算作迭代器,因此您可以直接使用它们。
  • using iterator = T*; 更具体地说,指向完整对象类型的指针count as contiguous iterators(见注释)

标签: c++ iterator c++20 c++-concepts


【解决方案1】:

据我所知,迭代器类需要 typedef,因此仅使用指针是不够的。这是一个例子:

#include <iterator>
#include <algorithm>

template<typename T, int SIZE>
class Array {
public:
    const T& operator[](int i) const { return mArray[i]; }
    T& operator[](int i) { return mArray[i]; }

private:
    T mArray[SIZE];

public:
struct iterator
{  
    using difference_type=std::ptrdiff_t;
    using value_type=std::remove_cv_t<T>;
    using pointer=T*;
    using reference=T&;
    using iterator_category=std::random_access_iterator_tag;
    using iterator_concept=std::contiguous_iterator_tag;
    using self_type=iterator;

    iterator(T *x) : ptr(x) {}
    T operator*() { return *ptr; }
    T operator->() { return ptr; }
    difference_type operator-(const iterator& rhs) { return ptr-rhs.ptr; }
    iterator& operator ++() { ++ptr; return *this;}
    bool operator !=(const iterator& rhs) { return ptr != rhs.ptr; }
private:
    T * ptr;
};

    iterator begin() { return &mArray[0]; }
    iterator end() { return &mArray[SIZE]; }
};

int foo(Array<int, 7>& a)
{
    int sum;
    for (auto x : a)
    {
        sum+=x;
    }

    return sum;
}

int goo(Array<int, 7>& a, int x)
{
    auto ret=std::find(a.begin(), a.end(), x);
    if (ret!=a.end()) return *ret;
    return 0;
}

请注意,对于 const 和非 const,您可能需要 const_iterator 和 reverse_iterators ...

【讨论】:

  • 帮了大忙 - 谢谢!
【解决方案2】:

学分

感谢@glenn-teitelbaum 为我指明了正确的方向。我想我设法弄清楚如何做到这一点。这花了很长时间,所以这有望为其他人省去麻烦。

[回答我自己的问题]

迭代器应该符合std::contiguous_iterator 的概念,所以我查看了cppreference 的必要部分。这个概念是这样定义的:

template<class I>
  concept contiguous_iterator =
    std::random_access_iterator<I> &&
    std::derived_from</*ITER_CONCEPT*/<I>, std::contiguous_iterator_tag> &&
    std::is_lvalue_reference_v<std::iter_reference_t<I>> &&
    std::same_as<
      std::iter_value_t<I>, std::remove_cvref_t<std::iter_reference_t<I>>
    > &&
    requires(const I& i) {
      { std::to_address(i) } ->
        std::same_as<std::add_pointer_t<std::iter_reference_t<I>>>;
    };

所以为了实现这个概念,我首先还要实现std::random_access_iteratorstd::is_derived_from&lt;[...]&gt;std::is_lvalue_reference_v&lt;[...]&gt;等等。这是一个递归过程,特别是因为std::random_access_iterator 构建在其他 4 种迭代器类型之上。由于这是一个 C++20 问题,我的目标是尽可能多地使用 C++20 特性(因为它大大简化了实现)。这是完整的迭代器实现:

template<typename T, int SIZE>
class Array
{
public:
    class Iterator
    {
    public:
        using iterator_category = std::contiguous_iterator_tag;
        using iterator_concept  = std::contiguous_iterator_tag;
        //using difference_type   = std::ptrdiff_t; // Likely the same
        using difference_type = typename std::iterator<
            std::contiguous_iterator_tag, T>::difference_type;
        //using value_type        = T;
        using value_type        = std::remove_cv_t<T>; // Using `T` seems sufficient
        using pointer           = T*;
        using reference         = T&;

        // constructor for Array<T,S>::begin() and Array<T,S>::end()
        Iterator(pointer ptr) : mPtr(ptr) {}

        // std::weakly_incrementable<I>
        Iterator& operator++() { ++mPtr; return *this; }
        Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }
        Iterator() : mPtr(nullptr/*&mArray[0]*/) {} // TODO: Unsure which is correct!

        // std::input_or_output_iterator<I>
        reference operator*() { return *mPtr; }

        // std::indirectly_readable<I>
        friend reference operator*(const Iterator& it) { return *(it.mPtr); }

        // std::input_iterator<I>
        // No actions were needed here!

        // std::forward_iterator<I>
        // In C++20, 'operator==' implies 'operator!='
        bool operator==(const Iterator& it) const { return mPtr == it.mPtr; }

        // std::bidirectional_iterator<I>
        Iterator& operator--() { --mPtr; return *this; }
        Iterator operator--(int) { Iterator tmp = *this; --(*this); return tmp; }

        // std::random_access_iterator<I>
        //     std::totally_ordered<I>
        std::weak_ordering operator<=>(const Iterator& it) const {
            return std::compare_three_way{}(mPtr, it.mPtr);
            // alternatively: `return mPtr <=> it.mPtr;`
        }
        //     std::sized_sentinel_for<I, I>
        difference_type operator-(const Iterator& it) const { return mPtr - it.mPtr; }
        //     std::iter_difference<I> operators
        Iterator& operator+=(difference_type diff) { mPtr += diff; return *this; }
        Iterator& operator-=(difference_type diff) { mPtr -= diff; return *this; }
        Iterator operator+(difference_type diff) const { return Iterator(mPtr + diff); }
        Iterator operator-(difference_type diff) const { return Iterator(mPtr - diff); }
        friend Iterator operator+(difference_type diff, const Iterator& it) {
            return it + diff;
        }
        friend Iterator operator-(difference_type diff, const Iterator& it) {
            return it - diff;
        }
        reference operator[](difference_type diff) const { return mPtr[diff]; }

        // std::contiguous_iterator<I>
        pointer operator->() const { return mPtr; }
        using element_type      = T;

    private:
        T* mPtr;
    };

    // === STATIC ASSERTS ===
    // - to verify correct Iterator implementation!
    static_assert(std::weakly_incrementable<Iterator>);
    static_assert(std::input_or_output_iterator<Iterator>);
    static_assert(std::indirectly_readable<Iterator>);
    static_assert(std::input_iterator<Iterator>);
    static_assert(std::incrementable<Iterator>);
    static_assert(std::forward_iterator<Iterator>);
    static_assert(std::bidirectional_iterator<Iterator>);
    static_assert(std::totally_ordered<Iterator>);
    static_assert(std::sized_sentinel_for<Iterator, Iterator>);
    static_assert(std::random_access_iterator<Iterator>);
    static_assert(std::is_lvalue_reference_v<std::iter_reference_t<Iterator>>);
    static_assert(std::same_as<std::iter_value_t<Iterator>,
                  std::remove_cvref_t<std::iter_reference_t<Iterator>>>);
    static_assert(std::contiguous_iterator<Iterator>);


    const T& operator[](int i) const {
        if (i < 0 || i >= SIZE) {
            throw std::runtime_error("Array index out of bounds");
        }
        return mArray[i];
    }

    T& operator[](int i) {
        if (i < 0 || i >= SIZE) {
            throw std::runtime_error("Array index out of bounds");
        }
        return mArray[i];
    }

    Iterator begin()  { return Iterator(&mArray[0]); }
    Iterator end()  { return Iterator(&mArray[SIZE]); }
private:
    T mArray[SIZE];
};

// Check that the Array class can be used as a contiguous_range.
static_assert(std::ranges::contiguous_range<Array<int, 10>>);

注意: using element_type = T; 是必需的,因为规范中的错误可能已修复。我找到了有关 here 的信息。添加此已修复的问题,std::to_address&lt;Iterator&gt; 无法编译,这是从 std::random_access_iteratorstd::contiguous_iterator 的最后一个缺失部分。

测试

我没有使用所有算法执行完整的测试套件,但我选择了一些取决于范围和std::random_access_iterator。一切顺利。我还依赖于将标准库头文件构建为模块单元,因为我想展示 C++20 功能如何协同工作。

import <stdexcept>;
import <iostream>;
import <iterator>;
import <algorithm>;
import <random>;
#include <memory>    // fails to build header unit!

template<typename T, int SIZE>
class Array
{
    [...]
};

int main()
{
    Array<int, 10> arr;
    for (int i = 0; i < 10; i++) arr[i] = i;
    // I need to call std::ragnes::shuffle since that depends on
    // std::random_access_iterator, so that is a minimum.
    // https://en.cppreference.com/w/cpp/algorithm/ranges/shuffle
    std::random_device rd;
    std::mt19937 gen{rd()};
    std::cout << "before random shuffle:\n";
    for (auto& i : arr) std::cout << i << ' ';
    std::ranges::shuffle(arr, gen);
    std::cout << "\nafter random shuffle:\n";
    for (auto& i : arr) std::cout << i << ' ';
    std::cout << '\n';

    // Also std::ranges::stable_sort is a good check (also random_access_iterator):
    // https://en.cppreference.com/w/cpp/algorithm/ranges/stable_sort
    std::cout << "after stable_sort:\n";
    std::ranges::stable_sort(arr);
    for (auto& i : arr) std::cout << i << ' ';
    std::cout << '\n';

    auto [min,max] = std::ranges::minmax(arr);
    std::cout << "min: " << min << ", max: " << max << '\n';

    return 0;
}

【讨论】:

  • 做得很好。您管理了一个比较棘手的位:bool operator==(const Iterator&amp; it) 必须是 const 成员函数。恕我直言,这一切仍然有点粗糙,但它正在变得更好。如 cmets 中所述,只需返回一个指针就足够了。
  • @GlennTeitelbaum 谢谢。与之前相比,带有概念的 static_cast 检查确实更容易实现。但很多时候编译器只是写了“我很困惑”然后停止解析。也许真正的 OOP 可以帮助使这更容易,比如实现一个接口会授予类某些迭代器特征,而不是这种废话。我没有尝试过只使用指针,可能会试一试。而且我当然不会也实现 const_iterator..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-02
  • 2012-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多