I am using variadic templates to implement the visitor pattern:
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;
}
Assuming there will be more classes which are visitable, you end up with a very long list of types when inheriting from the Visitable
and Visitor
templates. Also, if you want to add LinkElement to the visitable types accepted by this kind of visitor, you have to add it everywhere.
Since the same list of types is used when inheriting from the Visitor
and Visitable
(except that this one takes the addition type, the type of the class which is inheriting from it), I would like to implement a more elegant solution.
Is there a more preferable, cleaner way to define an alias for this list of types other than a macro?
Note: by macro I am referring to defining defining and using this instead of the actual list:
#define VISITABLE_TYPES IntegerElement, StringElement, BoxElement, ImageElement
// Add more types here
In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
In UML models, template parameters are formal parameters that once bound to actual values, called template arguments, make templates usable model elements. You can use template parameters to create general definitions of particular types of template.
Non-type template parameters are one of the few places in C++ where a visible distinction is made between class types and others: integral types, enum types, pointer types, point- to-member types, reference types and std::nullptr_t can all be used as non-type template parameters, but class types cannot.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With