如何在多态 类(包含向量和集合)中支持基于范围的循环?

How to support a range based loop in polymorphic classes (containing vector and set)?

我想遍历 class

中的一些项目
for (const auto i: myclass) { /* do stuff with i */}

为此,我想公开恰好将我的数据存储在 myclass 中的任何 STL 容器的迭代器。

我的 class 是多态的,具有以下层次结构:

#include <set>
#include <vector>

class Base_t {
public:
    //works for the set, but not for the vector
    //using iterator_t = decltype(std::declval<std::set<int>>().cbegin());

    //does not work, see error below.
    using iterator_t = std::iterator<std::input_iterator_tag, int>;

    virtual iterator_t begin() = 0;
    virtual iterator_t end() = 0;
};

class MyVector_t: public Base_t {
    std::vector<int> v;
public:
    iterator_t begin() override { return v.begin(); }
    iterator_t end() override {return v.end(); }
};

class MyTree_t: public Base_t {
    std::set<int> s;
public:
    iterator_t begin() override { return s.begin(); }
    iterator_t end() override {return s.end(); }
};

//#################################################
//the class using these things looks like:
//#################################################
class Worker_t {
    Base_t& container;  //polymorphic container
public:
    Worker_t(const bool UseTree): container(*CreateContainer(UseTree)) {}

    Base_t* CreateContainer(const bool UseTree) const {
        if (UseTree) { return new MyTree_t(); }
        else         { return new MyVector_t(); }
    }

    //need an iterator into the container to use it.
    void DoStuff() { for (const auto i: container) { printf("%i",i); } } 
};

int main(const int argc, const char* argv[]) {
    const auto UseTree = true;
    Worker_t worker1(UseTree);
    worker1.DoStuff();

    Worker_t worker2(!UseTree);
    worker2.DoStuff();
}

这给出了错误:

no viable conversion from returned value of type 'std::set::const_iterator' (aka '__tree_const_iterator<int, std::__tree_node<int, void *> *, long>') to function return type 'Base_t::it' (aka 'iterator<std::input_iterator_tag, int>')

我可以将层次结构设为 CRTP 设置,但我需要方法是虚拟的。

template <typename T>
class Base_t<T> {
    ....
}; 

class Derived: Base_t<Derived> {
    ...
};

对我不起作用,因为使用 classes 的代码只知道 Base_t,不知道其他任何东西,也就是说,使用这些东西的 class 看起来像:

class Worker_t {
    Base_t& container;  //polymorphic container
public:
    Worker_t(const bool UseTree): container(*CreateContainer(UseTree)) {}
   
    Base_t* CreateContainer(const bool UseTree) const {
        if (UseTree) { return new MyTree_t(); }
        else         { return new MyVector_t(); }
    }

    //need an iterator into the container to use it.
    void DoStuff() { for (const auto i&: container) { /* do stuff*/ } } 
};

使代码工作所需的最小更改是什么,最好使用虚拟方法?

郑重声明,我使用的是 c++20

Apple clang version 13.1.6 (clang-1316.0.19.2)
Target: x86_64-apple-darwin21.3.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

您正试图将运行时多态性强加到为 compile-time 多态性设计的盒子中。那自然会出问题。

迭代器机制基于能够向迭代器提问的compile-time机制。迭代器 tag 与基础 class 完全不同。 Iterator tag 类型在运行时不做任何事情;它们仅用于允许 compile-time 元编程(基于模板或 constexpr)检测迭代器的类型是否提供特定迭代器类别的接口。

在运行时多态性中,基础 class 定义了一切。 一切,直接而明确。派生的 classes 可以更改实现,但不能更改 compile-time 接口。

迭代器的 value_type(迭代器“指向”的类型)是一个 compile-time 结构。 vector<int> 具有与 vector<float> 不同的迭代器类型。它们都模拟了相同类型的迭代器 (random-access),但类型本身没有 runtime 关系。迭代器类型在很大程度上彼此不了解,即使它们具有相同的迭代器类别和 value_type.

std::array<int, 5>std::vector<int> 都有相同的 value_type 上的 RandomAccessIterators,但是这两种迭代器类型没有任何共同点。它们可以在相同的 compile-time 多态接口中使用,但它们不能在运行时互换使用。

迭代器类别不是基础classes

由于基础 class 定义了一个 virtual 接口,该接口 returns 是一个具体类型,因此此类迭代器的所有属性都由基础 定义 class。派生的 classes 可以提供值的实际范围,但迭代器的所有 compile-time 方面必须在编译时固定。这包括迭代器的基础类型。

如果您可以将所有派生的 classes 限制为使用具有特定迭代器的特定容器,那很好。但是如果你想让他们用不同的迭代器类型定义自己的容器,那就有问题了。

解决此问题的唯一方法是为您的基础 class 创建一个 type-erased 迭代器类型,它可以由任何迭代器填充,并且它本身使用运行时多态性来访问真正的迭代器类型。这实现起来并不简单,并且涉及大量开销(因为迭代器上的每个操作现在都是某种形式的动态调度)。

此外,这样的迭代器只能模拟一种特定类型的迭代器。因此,如果您希望派生 classes 能够将数据存储在 RandomAccessIterator 或 ForwardIterator 中,则您的 type-erased 迭代器必须仅将自身公开为 ForwardIterator:最小公分母。