当然。
您的容器变成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)...};
}
overload 在c++17 中更简单:
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 返回的内容中(在迭代器内部),但这需要复杂的分支迭代器实现或调用者访问迭代器。并且由于开始/结束迭代器类型可能是绑定的,因此您无论如何都希望返回一个范围,这样访问才有意义。无论如何,这会让你回到容器解决方案的一半。
您也可以在variant 之外实现此功能,但作为一般规则,早期对变量的操作不能在同一代码范围内更改后面的类型。它可以用于调度以“连续传递样式”传递的可调用对象,其中两种实现都将被编译,但在运行时选择一个(通过分支)。编译器可能会意识到访问将关闭哪个分支并且死代码消除另一个,但另一个分支仍然需要是有效代码。
如果您想要完全动态类型的对象,您将至少损失 2 到 10 倍的速度(这是支持此功能的语言所做的),这很难通过一个元素循环的迭代效率来恢复。这将与在返回的迭代器中存储等效的变体(可能是虚拟接口或其他)并使其在运行时复杂地处理分支有关。由于您的目标是性能,因此这是不切实际的。
理论上,C++ 可以根据对变量的操作来更改变量的类型。即,一种理论语言,其中
Container c;
是“空容器”类型,则:
c.add(foo);
现在c 将静态类型更改为“单元素容器”,然后
c.add(foo);
并且c 将静态类型更改为“多元素容器”。
但这不是 C++ 类型模型。你可以像上面那样模拟它(在运行时),但它不一样。