为类型列表创建别名并将其作为模板参数传递

时间:2016-08-31 06:25:38

标签: c++ c++11 c++14 variadic-templates crtp

我正在使用可变参数模板来实现访问者模式:

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

假设有更多可访问的类,从VisitableVisitor模板继承时,最终会得到很长的类型列表。此外,如果要将LinkElement添加到此类访问者接受的可访问类型,则必须将其添加到任何位置。

由于从VisitorVisitable继承时使用了相同的类型列表(除了这个类型采用了加法类型,即继承自它的类的类型),我会喜欢实施更优雅的解决方案。

是否有一种更优选,更清晰的方法来为除宏以外的此类型列表定义别名?

注意:宏我指的是定义和使用它而不是实际列表:

#define VISITABLE_TYPES IntegerElement, StringElement, BoxElement, ImageElement
// Add more types here

1 个答案:

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