如何惯用地编写const迭代器?

时间:2018-12-29 04:09:34

标签: c++ templates

我正在编写一个链表实现,但是我一直陷于如何实现与常规迭代器一起通过cbegin()访问的const迭代器的问题。这是我尝试解决的方法:

#include <iostream>

using namespace std;

template <typename T>
class ListNode {
public:
    ListNode<T>* next_;
    ListNode<T>* prev_;
    T data_;

    ListNode():
        next_(nullptr),
        prev_(nullptr)
    {}
};

template <typename T>
class ListIterator
{
    typedef ListNode<T> node;
    typedef ListNode<T>* pointer;
    pointer p_;

public:
    ListIterator(pointer p) : p_(p) {}
    T& operator*() { return p_->data_; }
};

template<typename T>
class List
{
public:
    typedef ListNode<T> node;
    typedef ListNode<T>* pointer;
    typedef ListIterator<T> iterator;
    typedef ListIterator<const T> constIterator;

    List() :
        head_(nullptr),
        tail_(nullptr),
        size_(0)
    {}

    void pushBack(pointer p) {
        p->next_ = nullptr;
        p->prev_ = nullptr;

        if (size_ == 0) {
            head_ = p;
        } else {
            tail_->next_ = p;
            p->prev_ = tail_;
        }

        tail_ = p;
        ++size_;
    }

    iterator begin() { return head_; }
    iterator end() { return nullptr; }
    constIterator cbegin() { return head_; }
    constIterator cend() { return nullptr; }

private:
    pointer head_;
    pointer tail_;
    unsigned int size_;
};

class Dog {
public:
    int age;
};

int main() {
    auto list = List<Dog>();
    auto dogNode = ListNode<Dog>();
    list.pushBack(&dogNode);
    auto b = list.cbegin();
}

当我尝试运行此命令时,出现错误 error: no viable conversion from returned value of type 'List<Dog>::pointer' (aka 'ListNode<Dog> *') to function return type 'List<Dog>::constIterator' (aka 'ListIterator<const Dog>')

这个错误对我来说很有意义,但我想不出一个不错的解决方法,该方法不涉及编写单独的ConstListIterator类(此方法有效,但是复制粘贴所有操作符重载感觉不对。 ListIterator中的代码-此示例中省略了大部分代码。)

this question中,作者使用了ListIterator参数化TNode的方法,在这种情况下,它将是ListNode<Dog>而不是Dog。这种方法的问题在于operator*不能返回实际的Dog,因为ListIterator类不知道类型名Dog。因此,该解决方案仅以data_类型进行硬编码,因此您将失去元编程的所有好处。

是否有使它正常工作的技巧,还是应该只另设一个ConstListIterator类?

谢谢!

2 个答案:

答案 0 :(得分:2)

发生错误的原因是,您试图从具有返回类型head的函数(也称为ListNode<T>*)返回constIterator类型的ListIterator<const T>。 / p>

要回答您的问题,iteratorconst_iterator之间的唯一区别是const_iterator返回对容器value_type的const引用,而不仅仅是参考。因此,将const T作为模板参数传递应该会产生所需的功能。由于operator*返回T&,因此如果T改为const T,则它返回const T&,这是正确的。如上所述,您遇到的问题似乎是因为您从iterator函数返回了错误的类型。

您的迭代器函数应如下所示:

iterator begin() { return iterator{head_}; }
iterator end() { return iterator{nullptr}; }
constIterator cbegin() { return constIterator{head_}; }
constIterator cend() { return constIterator{nullptr}; }

这将返回iteratorconstIterator对象,这些对象是使用指针head_nullptr构造的。

您用于迭代器的模板参数,列表的value_type是正确的。

答案 1 :(得分:2)

将节点类型作为迭代器的模板参数是合理且允许的

using constIterator=ListIterator<const ListNode<T>>;

可方便地排除结构修改和内容修改。它使cbegin可以按书面形式工作(在const的类型上隐式添加head_)。它还允许T本身就是const(适用于链表:它never needs to relocate anything)。

要定义operator*,只需使用auto

auto& operator*() const {return p_->data_;}

*p_的常量性会延续到data_,然后返回到返回类型。

在C ++ 14之前

在C ++ 11中,您可以为ListNode配备

using value_type=T;

并使用is_constconditional之类的类型特征从const T派生const ListNode<T>

在C ++ 03中,您可以为此直接编写基于SFINAE的特征。