Nikopol Nikopol - 2 months ago 5
C++ Question

Create alias for a list of types and passing it as a template parameter

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

Answer

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;
}