简体   繁体   English

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

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

I would like to iterate over some items in my class我想迭代我的 class 中的一些项目

for (const auto i: myclass) { /* do stuff with i */}

For this I want to expose the iterator of whatever STL container happens to be storing my data inside myclass .为此,我想公开任何 STL 容器恰好将我的数据存储在myclass中的迭代器。

My class is polymorphic and has the following hierarchy:我的 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();
}

This gives the error:这给出了错误:

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>')从“std::set::const_iterator”(又名“__tree_const_iterator<int, std::__tree_node<int, void *> *, long>”)类型的返回值到 function 返回类型“Base_t::it”没有可行的转换(又名 'iterator<std::input_iterator_tag, int>')

I could make the hierarchy a CRTP setup, but I need the methods to be virtual.我可以使层次结构成为 CRTP 设置,但我需要这些方法是虚拟的。

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

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

Does not work for me, because the code using the classes only knows about Base_t, not about anything else, that is, the class using these things looks like:对我不起作用,因为使用类的代码只知道 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*/ } } 
};

What would be the minimal changes needed to make the code work, preferably using virtual methods?使代码工作所需的最小更改是什么,最好使用虚拟方法?

For the record I'm using c++20作为记录,我使用的是 c++20

Apple clang version 13.1.6 (clang-1316.0.19.2)苹果 clang 版本 13.1.6 (clang-1316.0.19.2)
Target: x86_64-apple-darwin21.3.0目标:x86_64-apple-darwin21.3.0
Thread model: posix线程 model:posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin安装目录:/Library/Developer/CommandLineTools/usr/bin

You're attempting to force runtime polymorphism into a box designed for compile-time polymorphism.您正试图将运行时多态性强制放入为编译时多态性设计的盒子中。 That will naturally create problems.这自然会产生问题。

The iterator mechanism is based on compile-time mechanisms being able to ask questions of the iterator.迭代器机制基于能够询问迭代器问题的编译时机制。 Iterator tag s are nothing like base classes.迭代器tag与基类完全不同。 Iterator tag types do not do anything at runtime;迭代器tag类型在运行时不做任何事情; they are solely used to allow compile-time metaprogramming (template or constexpr -based) to detect if an iterator's type provides the interface of a particular iterator category.它们仅用于允许编译时元编程(基于模板或constexpr )检测迭代器的类型是否提供特定迭代器类别的接口。

In runtime polymorphism, the base class defines everything.在运行时多态中,基础 class 定义了一切。 Everything , directly and explicitly.一切,直接而明确。 Derived classes can change the implementation, but they cannot change the compile-time interface.派生类可以更改实现,但不能更改编译时接口。

An iterator's value_type (the type the iterator "points" to) is a compile-time construct.迭代器的 value_type(迭代器“指向”的类型)是一个编译时构造。 vector<int> has a different iterator type from vector<float> . vector<int>具有与vector<float>不同的迭代器类型。 They both model the same kind of iterator (random-access), but the types themselves have no runtime relationship.它们都是 model 同一种迭代器(随机访问),但类型本身没有运行时关系。 Iterator types are largely unaware of each other, even if they have the same iterator category and value_type .迭代器类型在很大程度上不知道彼此,即使它们具有相同的迭代器类别和value_type

std::array<int, 5> and std::vector<int> both have RandomAccessIterators over the same value_type , but the two iterator types have nothing in common. std::array<int, 5>std::vector<int>在相同的value_type上都有 RandomAccessIterators,但是这两种迭代器类型没有任何共同之处。 They can be used in the same compile-time polymorphic interfaces, but they cannot be used interchangeably at runtime.它们可以在相同的编译时多态接口中使用,但它们不能在运行时互换使用。

Iterator categories are not base classes .迭代器类别不是基类

Since the base class defines a virtual interface which returns a concrete type, all properties of such iterators are defined by the base class .由于基础 class 定义了一个返回具体类型的virtual接口,因此此类迭代器的所有属性都由基础 class定义。 Derived classes can provide the actual range of values, but all compile-time aspects of the iterators must be fixed at compile time.派生类可以提供实际的值范围,但迭代器的所有编译时方面都必须在编译时固定。 This includes the underlying type of the iterator.这包括迭代器的基础类型。

If you are fine with restricting all of your derived classes to using a particular container with a particular iterator, that's fine.如果您可以将所有派生类限制为使用具有特定迭代器的特定容器,那很好。 But if you want them to define their own containers with different iterator types, that's a problem.但是如果你想让他们用不同的迭代器类型定义他们自己的容器,那就有问题了。

The only way around this is to create a type-erased iterator type for your base class, which can be filled in by any iterator and itself uses runtime polymorphism to access the real iterator type.解决这个问题的唯一方法是为您的基本 class 创建一个类型擦除的迭代器类型,它可以由任何迭代器填充,并且它本身使用运行时多态性来访问真正的迭代器类型。 This isn't trivial to implement and involves significant overhead (as every operation on the iterator is now a dynamic dispatch of some form).这实现起来并不简单,并且涉及大量开销(因为迭代器上的每个操作现在都是某种形式的动态调度)。

Also, such an iterator can only ever model a specific kind of iterator.此外,这样的迭代器只能 model 特定类型的迭代器。 So if you want derived classes to be able to store data in a RandomAccessIterator or a ForwardIterator, your type-erased iterator must only expose itself as a ForwardIterator: the lowest common denominator.因此,如果您希望派生类能够将数据存储在 RandomAccessIterator 或 ForwardIterator 中,您的类型擦除迭代器必须仅将其自身公开为 ForwardIterator:最低公分母。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM