我想使用模板元编程使用> = C ++ 14来实现完全通用的访问者模式。我已经找到了一个很好的方法来概括访问者本身,但我在定义Visitables方面遇到了麻烦。下面的代码有效,但我希望main
中注释掉的代码能够正常工作;特别是,我希望能够拥有一组Visitables并将访问者应用于每个元素。
我正在尝试用C ++做什么?
我试过的事情:
class X : public Visitable<X>
这解决了在accept
中没有合适的X
方法的问题,但是导致编译器无法解决的模糊性X/A
和X/B
。accept
中没有继承的空X
方法;有效,但accept
和A
的专业B
方法从未被称为。Visitor
替换为带有函数模板visit
的常规类,用于任意类型;并没有真正改变语义,但不太可读恕我直言#include <iostream>
#include <vector>
template <typename I>
class Visitable {
public:
template <typename Visitor>
void accept(Visitor&& v) const {
v.visit(static_cast<const I&>(*this));
}
};
template <typename T, typename... Ts>
class Visitor : public Visitor<Ts...> {
public:
virtual void visit(const T& t);
};
template<typename T>
class Visitor<T> {
public:
virtual void visit(const T& t);
};
struct X {
// template <typename V> void accept(V&& v) const {};
};
struct A : public X, public Visitable<A> {};
struct B : public X, public Visitable<B> {};
class MyVisitor : public Visitor<A, B> {
public:
void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};
int main() {
MyVisitor v {};
// std::vector<X> elems { A(), B() };
// for (const auto& x : elems) {
// x.accept(v);
// }
A().accept(v);
B().accept(v);
}
struct empty_t{};
template <class I, class B=empty_t>
class Visitable:public B {
public:
// ...
struct X : Visitable<X>{
};
struct A : Visitable<A,X> {};
struct B : Visitable<B,X> {};
但请注意,此处的调度是静态的。而你的载体含有X
s而不是A
s或B
s。
你可能想要
template <class Visitor>
struct IVisitable {
virtual void accept(Visitor const& v) const = 0;
protected:
~IVisitable(){}
};
template <class I, class Visitor, class B=IVisitable<Visitor>>
struct Visitable {
virtual void accept(Visitor const& v) const override {
v.visit(static_cast<const I&>(*this));
}
};
越来越近了。
struct A; struct B; struct X;
struct X:Visitable<X, Visitor<A,B,X>> {
};
struct A :Visitable<A, Visitor<A,B,X>, X> {};
struct B :Visitable<B, Visitor<A,B,X>, X> {};
这仍然不能做你想要的,因为你有一个值向量。多态值需要更多的工作。
使它成为X的独特ptrs矢量,并添加virtual ~X(){}
和一些*
和make_unique
s,这将做你想要的。
您当前的解决方案存在一些问题:
A
和B
值正确存储在集合中,以便您可以访问集合中的每个元素。 X
没有实现这一点,因为没有办法要求X
的子类也是Visitable
类模板的实例化的子类。vector<A>
或vector<B>
,在这种情况下,您将失去在同一集合中存储不同可访问类型的值的能力。您需要一种方法来在运行时处理访问者/可访问不匹配的场景,或者您需要更复杂的模板结构。vector
将其元素连续存储在内存中,因此必须为每个元素假定一定的常量大小;就其性质而言,多态值具有未知的大小。解决方案是使用一组(智能)指针来引用堆上其他位置的多态值。这是您原始代码的工作改编:
#include <iostream>
#include <vector>
#include <memory>
template<typename T>
class Visitor;
class VisitorBase {
public:
virtual ~VisitorBase() {}
};
class VisitableBase {
public:
virtual void accept(VisitorBase& v) const = 0;
virtual ~VisitableBase() {}
};
template <typename I>
class Visitable : public VisitableBase {
public:
virtual void accept(VisitorBase& v) const {
auto visitor = dynamic_cast<Visitor<I> *>(&v);
if (visitor == nullptr) {
// TODO: handle invalid visitor type here
} else {
visitor->visit(dynamic_cast<const I &>(*this));
}
}
};
template<typename T>
class Visitor : public virtual VisitorBase {
public:
virtual void visit(const T& t) = 0;
};
struct A : public Visitable<A> {};
struct B : public Visitable<B> {};
class MyVisitor : public Visitor<A>, public Visitor<B> {
public:
void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};
int main() {
MyVisitor v {};
std::vector<std::shared_ptr<VisitableBase>> elems {
std::dynamic_pointer_cast<VisitableBase>(std::make_shared<A>()),
std::dynamic_pointer_cast<VisitableBase>(std::make_shared<B>())
};
for (const auto& x : elems) {
x->accept(v);
}
A().accept(v);
B().accept(v);
}