如何在多态 类(包含向量和集合)中支持基于范围的循环?
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:最小公分母。
我想遍历 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:最小公分母。