我正在使用可变参数模板来实现访问者模式:
template<typename... Types>
class Visitor;
template<typename Type>
class Visitor<Type> {
public:
virtual void visit(Type &visitable) = 0;
};
template<typename Type, typename... Types>
class Visitor<Type, Types...>: public Visitor<Types...> {
public:
using Visitor<Types...>::visit;
virtual void visit(Type &visitable) = 0;
};
template<typename... Types>
class VisitableInterface {
public:
virtual void accept(Visitor<Types...> &visitor) = 0;
};
template<typename Derived, typename... Types>
class Visitable : public VisitableInterface<Types...> {
public:
virtual void accept(Visitor<Types...> &visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
class IntegerElement;
class StringElement;
class BoxElement;
class ImageElement;
class IntegerElement: public Visitable<IntegerElement, IntegerElement, StringElement, BoxElement,
ImageElement> {};
class StringElement: public Visitable<StringElement, IntegerElement, StringElement, BoxElement,
ImageElement> {};
class BoxElement: public Visitable<BoxElement, IntegerElement, StringElement, BoxElement,
ImageElement> {};
class ImageElement: public Visitable<ImageElement, IntegerElement, StringElement, BoxElement,
ImageElement> {};
class RenderEngine : public Visitor<IntegerElement, StringElement, BoxElement, ImageElement>
{
virtual void visit(IntegerElement& e) {};
virtual void visit(StringElement& e) {};
virtual void visit(BoxElement& e) {};
virtual void visit(ImageElement& e) {};
};
int main(void)
{
RenderEngine renderEngine;
return 0;
}
假设有更多可访问的类,从Visitable
和Visitor
模板继承时,最终会得到很长的类型列表。此外,如果要将LinkElement添加到此类访问者接受的可访问类型,则必须将其添加到任何位置。
由于从Visitor
和Visitable
继承时使用了相同的类型列表(除了这个类型采用了加法类型,即继承自它的类的类型),我会喜欢实施更优雅的解决方案。
是否有一种更优选,更清晰的方法来为除宏以外的此类型列表定义别名?
注意:宏我指的是定义和使用它而不是实际列表:
#define VISITABLE_TYPES IntegerElement, StringElement, BoxElement, ImageElement
// Add more types here
答案 0 :(得分:1)
std::tuple
and using
are your friends.
If you define Visitable
in this way
template <typename, typename>
class Visitable;
template<typename Derived, typename... Types>
class Visitable<Derived, std::tuple<Types...>> : public VisitableInterface<Types...> {
public:
virtual void accept(Visitor<Types...> &visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
and add, via using
, something that substitute the macro idea
using tupleT = std::tuple<IntegerElement, StringElement, BoxElement, ImageElement>;
the definition of your elements become simply
class IntegerElement: public Visitable<IntegerElement, tupleT> {};
class StringElement: public Visitable<StringElement, tupleT> {};
class BoxElement: public Visitable<BoxElement, tupleT> {};
class ImageElement: public Visitable<ImageElement, tupleT> {};
Your example modified
#include <iostream>
template<typename... Types>
class Visitor;
template<typename Type>
class Visitor<Type> {
public:
virtual void visit(Type &visitable) = 0;
};
template<typename Type, typename... Types>
class Visitor<Type, Types...>: public Visitor<Types...> {
public:
using Visitor<Types...>::visit;
virtual void visit(Type &visitable) = 0;
};
template<typename... Types>
class VisitableInterface {
public:
virtual void accept(Visitor<Types...> &visitor) = 0;
};
template <typename, typename>
class Visitable;
template<typename Derived, typename... Types>
class Visitable<Derived, std::tuple<Types...>> : public VisitableInterface<Types...> {
public:
virtual void accept(Visitor<Types...> &visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
class IntegerElement;
class StringElement;
class BoxElement;
class ImageElement;
using tupleT = std::tuple<IntegerElement, StringElement, BoxElement, ImageElement>;
class IntegerElement: public Visitable<IntegerElement, tupleT> {};
class StringElement: public Visitable<StringElement, tupleT> {};
class BoxElement: public Visitable<BoxElement, tupleT> {};
class ImageElement: public Visitable<ImageElement, tupleT> {};
class RenderEngine : public Visitor<IntegerElement, StringElement, BoxElement, ImageElement>
{
public:
virtual void visit(IntegerElement& e) { std::cout << "visit Int\n"; };
virtual void visit(StringElement& e) { std::cout << "visit Str\n"; };
virtual void visit(BoxElement& e) { std::cout << "visit Box\n"; };
virtual void visit(ImageElement& e) { std::cout << "visit Img\n"; };
};
int main(void)
{
RenderEngine renderEngine;
IntegerElement intE;
StringElement strE;
BoxElement boxE;
ImageElement imgE;
renderEngine.visit(intE);
renderEngine.visit(strE);
renderEngine.visit(boxE);
renderEngine.visit(imgE);
return 0;
}
--- EDIT ---
I try to respond to your comment-questions
why was the template class Visitable; needed before defining the actual template?
I don't know if it's possible to do this in a simpler way but... it's because we need "extract" the types from a std::tuple
. So you need a general definition (template <typename, typename>
to be able to receive the std::tuple<something>
type and you need a specialization so you can extract the someting
types.
the same neat trick can be also done for the Visitor template by defining an additional template that takes a std::tuple as template parameter. Can you add this to your answer as well, please?
Yes, it's possible.
But you have to modify VisitableInterface
and RenderEngine
too.
A big change for a little improvement (IMHO); just for use tupleT
defining RenderEngine
.
Anyway, your example become
#include <iostream>
template<typename>
class Visitor;
template<typename Type>
class Visitor<std::tuple<Type>> {
public:
virtual void visit(Type &visitable) = 0;
};
template<typename Type, typename... Types>
class Visitor<std::tuple<Type, Types...>>: public Visitor<std::tuple<Types...>> {
public:
using Visitor<std::tuple<Types...>>::visit;
virtual void visit(Type &visitable) = 0;
};
template<typename... Types>
class VisitableInterface {
public:
virtual void accept(Visitor<std::tuple<Types...>> &visitor) = 0;
};
template <typename, typename>
class Visitable;
template<typename Derived, typename... Types>
class Visitable<Derived, std::tuple<Types...>> : public VisitableInterface<Types...> {
public:
virtual void accept(Visitor<std::tuple<Types...>> &visitor) {
visitor.visit(static_cast<Derived&>(*this));
}
};
class IntegerElement;
class StringElement;
class BoxElement;
class ImageElement;
using tupleT = std::tuple<IntegerElement, StringElement, BoxElement, ImageElement>;
class IntegerElement: public Visitable<IntegerElement, tupleT> {};
class StringElement: public Visitable<StringElement, tupleT> {};
class BoxElement: public Visitable<BoxElement, tupleT> {};
class ImageElement: public Visitable<ImageElement, tupleT> {};
class RenderEngine : public Visitor<tupleT>
{
public:
virtual void visit(IntegerElement& e) { std::cout << "visit Int\n"; };
virtual void visit(StringElement& e) { std::cout << "visit Str\n"; };
virtual void visit(BoxElement& e) { std::cout << "visit Box\n"; };
virtual void visit(ImageElement& e) { std::cout << "visit Img\n"; };
};
int main(void)
{
RenderEngine renderEngine;
IntegerElement intE;
StringElement strE;
BoxElement boxE;
ImageElement imgE;
renderEngine.visit(intE);
renderEngine.visit(strE);
renderEngine.visit(boxE);
renderEngine.visit(imgE);
return 0;
}