有没有办法构建一个包含两个实现的迭代器类:一个包含任意数量元素的容器的一般实现和一个特殊情况(非常快)的实现,当容器包含一个不使用虚函数和动态的元素时多态?
目前,我有:
struct Container {
struct FastIterator;
struct SlowIterator;
void add(...) { ... }
SlowIterator begin_slow() { ... }
FastIterator begin_fast() { ... }
};
相反,我希望:
struct Container {
struct Iterator;
void add(...) { ... }
Iterator begin() { // select between fast and slow based on the contents of the container }
};
这样:
void f() {
Container c;
c.add(...);
Container::Iterator it = c.begin(); // uses FastIterator hidden by the Iterator type
}
void f2() {
Container c;
c.add(...);
c.add(...);
Container::Iterator it = c.begin(); // use SlowIterator hidden by the iterator type
}
当然,显而易见的方法是在Iterator实现中使用虚函数或委托来从一个案例切换到另一个案例,但是我测试了与直接使用Slow / Fast相比,这减缓了很多次迭代迭代器。
由于在调用begin()期间决定使用哪个实现的所有信息都可用,我认为有一种方法可以使用某种编译时多态/技巧来避免任何类型的间接。
另外,我真的不希望用户必须决定是否应该调用begin_fast()或begin_slow(),这应该由Iterator类自动处理和隐藏。
有办法吗?
由于
答案 0 :(得分:2)
不确定
您的容器变为两个不同状态的std::variant
,即“单个元素”状态和“多个元素”状态(可能是“零元素”状态)。
成员函数add
可以将零元素或单元素容器转换为单元素或多元素函数。同样,在某些情况下,remove
可能会相反。
变体本身没有begin
或end
。相反,用户必须std::visit
使用可以接受的函数对象。
template<class T>
struct Container:
std::variant<std::array<T,0>, std::array<T,1>, std::vector<T>>
{
void add(T t) {
std::visit(
overload(
[&](std::array<T,0>& self) {
*this = std::array<T,1>{{std::move(t)}};
},
[&](std::array<T,1>& self) {
std::array<T,1> tmp = std::move(self);
*this = std::vector<T>{tmp[0], std::move(t)};
},
[&](std::vector<T>& self) {
self.push_back( std::move(t) );
}
),
*this
);
}
};
boost
有variant
,效果相似。 overload
仅仅是
struct tag {};
template<class...Fs>
struct overload_t {overload_t(tag){}};
template<class F0, class F1, class...Fs>
struct overload_t: overload_t<F0>, overload_t<F1, Fs...> {
using overload_t<F0>::operator();
using overload_t<F1, Fs...>::operator();
template<class A0, class A1, class...Args>
overload_t( tag, A0&&a0, A1&&a1, Args&&...args ):
overload_t<F0>( tag{}, std::forward<A0>(a0)),
overload_t<F1, Fs...>(tag{}, std::forward<A1>(a1), std::forward<Args>(args)...)
{}
};
template<class F>
struct overload_t:F {
using F::operator();
template<class A>
overload_t( tag, A&& a ):F(std::forward<A>(a)){}
};
template<class...Fs>
overload_t<std::decay_t<Fs>...> overload(Fs&&...fs) {
return {tag{}, std::forward<Fs>(fs)...};
}
在c++17中 overload
非常容易:
template<class...Fs>
struct overload:Fs{
using Fs::operator();
};
template<class...Fs>
overload->overload<Fs...>;
并使用{}
代替()
。
在c++14中使用此内容如下:
Container<int> bob = get_container();
std::visit( [](auto&& bob){
for (int x:bob) {
std::cout << x << "\n";
}
}, bob );
对于0和1的情况,循环的大小将完全为编译器所知。
在c++11中,您必须编写外部模板函数对象而不是内联lambda。
您可以将variant
部分移出Container
并转移到begin
返回(迭代器内部),但这需要复杂的分支迭代器实现或访问者来访问在迭代器上。并且由于开始/结束迭代器类型可能是并列的,所以无论如何都要返回一个范围,这样访问才有意义。无论如何,这会让你回到Container解决方案的一半。
您也可以在variant
之外实现此功能,但作为一般规则,对变量的早期操作不能更改同一代码范围中的更新类型。 可以用于在“继续传递样式”中传递的可调用对象上进行调度,其中两个实现都将被编译,但在运行时选择一个(通过分支)。编译器有可能实现访问将关闭哪个分支而死代码消除另一个分支,但另一个分支仍然需要是有效代码。
如果你想要完全动态类型化的对象,你至少会失去2到10倍的速度(这是支持它的语言),这很难通过一个元素循环的迭代效率来恢复。这与在返回的迭代器中存储变量等效(可能是虚拟接口或其他)并使其在运行时复杂地处理分支有关。由于你的目标是表现,这是不切实际的。
理论上,C ++可以根据对它们的操作来改变变量的类型。即,理论语言
Container c;
是“空容器”类型,然后是:
c.add(foo);
现在c
将静态类型更改为“单个元素容器”,然后
c.add(foo);
和c
将静态类型更改为“多元素容器”。
但那不是C ++类型的模型。您可以像上面一样(在运行时)模拟它,但它不一样。