Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 738
Nikopol Avatar asked Aug 31 '16 06:08

Nikopol


People also ask

What is a template template parameter in C++?

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.

Can a template be a template parameter?

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.)

What is a template parameter?

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.

Which of the following types Cannot be used for a non type template parameter?

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.


1 Answers

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;
}
like image 71
max66 Avatar answered Sep 29 '22 09:09

max66