c++ - 可变访客类型推导

标签 c++ templates runtime variadic-templates

我有一个包含节点的场景图。每个节点都有一个转换和几个组件。我有一个 2d 场景编辑器,目前正在执行以下操作来选择要编辑的组件:

        //Within the node class I can grab a list of components
        template<typename ComponentType = Component>
        std::vector<SafeComponent<ComponentType>> components(bool exactType = true) const {
            std::lock_guard<std::recursive_mutex> guard(lock);
            std::vector<SafeComponent<ComponentType>> matchingComponents;
            if (exactType) {
                for (auto&& currentComponent : childComponents) {
                    if (typeid(currentComponent.get()) == typeid(ComponentType)) {
                        matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), std::static_pointer_cast<ComponentType>(currentComponent)));
                    }
                }
            } else {
                for (auto&& currentComponent : childComponents) {
                    auto castComponent = std::dynamic_pointer_cast<ComponentType>(currentComponent);
                    if (castComponent) {
                        matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), castComponent));
                    }
                }
            }
            return matchingComponents;
        }

然后在我的编辑器代码中有以下内容

auto componentList = controls->elementToEdit->components(false);

for (auto&& component : componentList) {
    std::cout << typeid(*component.self().get()).name() << std::endl;
    if (typeid(*component.self().get()) == typeid(MV::Scene::Sprite)) {
        auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditSprite"), buttonSize, MV::toWide("S: " + component->id()));
        button->onAccept.connect("click", [&,component](std::shared_ptr<MV::Scene::Clickable>) {
            panel.loadPanel<SelectedRectangleEditorPanel>(std::make_shared<EditableRectangle>(component.cast<MV::Scene::Sprite>(), panel.content(), panel.resources().mouse));
        });
    }else if (typeid(*component.self().get()) == typeid(MV::Scene::Grid)) {
        auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditGrid"), buttonSize, MV::toWide("G: " + component->id()));
        button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
            panel.loadPanel<SelectedGridEditorPanel>(std::make_shared<EditableGrid>(component.cast<MV::Scene::Grid>(), panel.content(), panel.resources().mouse));
        });
    }else if (typeid(*component.self().get()) == typeid(MV::Scene::Emitter)) {
        auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditEmitter"), buttonSize, MV::toWide("E: " + component->id()));
        button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
            panel.loadPanel<SelectedEmitterEditorPanel>(std::make_shared<EditableEmitter>(component.cast<MV::Scene::Emitter>(), panel.content(), panel.resources().mouse));
        });
    }
}

我想利用访问者模式,但我不想通过创建一个必须针对每种可能类型的组件修改的“访问者”来将我的类对外封闭。值得庆幸的是,我找到了一个很酷的概念性解决方法。

https://ciaranm.wordpress.com/2010/07/15/generic-lambda-visitors-or-writing-haskell-in-c0x-part-4/

如果我可以修改我的 Node::components 方法以返回

std::vector<OneOf<[ParameterPackPseudoCode]>> 

我可以这样写:

auto componentList = controls->elementToEdit->components<Sprite, Grid, Emitter>();
for (auto&& component : componentList) {
    where(component,
        [&](const SafeComponent<Sprite> &sprite){
            auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditSprite"), buttonSize, MV::toWide("S: " + component->id()));
            button->onAccept.connect("click", [&,component](std::shared_ptr<MV::Scene::Clickable>) {
                panel.loadPanel<SelectedRectangleEditorPanel>(std::make_shared<EditableRectangle>(sprite, panel.content(), panel.resources().mouse));
            });
        },
        [&](const SafeComponent<Grid> &grid){
            auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditGrid"), buttonSize, MV::toWide("G: " + component->id()));
            button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
                panel.loadPanel<SelectedGridEditorPanel>(std::make_shared<EditableGrid>(grid, panel.content(), panel.resources().mouse));
            });
        },
        [&](const SafeComponent<Emitter> &emitter){
            auto button = makeButton(grid, *panel.resources().textLibrary, *panel.resources().mouse, MV::guid("EditEmitter"), buttonSize, MV::toWide("E: " + component->id()));
            button->onAccept.connect("click", [&, component](std::shared_ptr<MV::Scene::Clickable>) {
                panel.loadPanel<SelectedEmitterEditorPanel>(std::make_shared<EditableEmitter>(emitter, panel.content(), panel.resources().mouse));
            });
        }
    );
}

我的问题是,我想写这样的东西:

myNode->components<Sprite, Grid, Emitter>();

然后返回:

std::vector<OneOf<SafeComponent<Sprite>, SafeComponent<Grid>, SafeComponent<Emitter>>>

这有两个我真的不知道要解决的语法或方法的问题:

1) 我需要能够将 Sprite、Grid、Emitter 转换为:

template<typename ...ComponentList> Node::components

进入

OneOf<SafeComponent<Sprite>, SafeComponent<Grid>, SafeComponent<Emitter>>

2) 我需要能够对参数包 (...ComponentList) 中的每个项目运行 typeid,以确保它实际匹配我在 Node 中迭代我的组件列表时正在寻找的基础类型,以便 push_back仅将所需的元素放入 matchingComponents vector 中。

如果你看一下我目前是如何检查单一类型的,它非常简单,但我真正需要的是:

if(TypeMatchesAnyOf<...ComponentList>(currentComponent)){
    matchingComponents.push_back(SafeComponent<ComponentType>(shared_from_this(), std::static_pointer_cast<TypeMatched<...ComponentList>(currentComponent)::type>(currentComponent)));
}

TypeMatchesAnyOf 和 TypeMatched::type 是我认为不可或缺的两个组成部分。

最佳答案

经过大量实验后,我能够解决我的问题。本质上,我在这里所做的是根据可变模板参数过滤对象 vector 。这是混合运行时和编译时类型检查的挑战。

希望这对其他人有帮助,因为我相信这是一种有用的模式,可以按类型过滤组件并应用访问者,而无需将访问者的任何知识烘焙到节点类中。这基本上解决了访问者模式需要静态维护的双重调度类的问题。

https://ideone.com/19gTnw

相关的包括/使用标准(不要在标题中使用标准,我只在这里包括它是因为我想包括所有的 ideone 链接,这样你就可以复制/粘贴这个答案的每个部分来创建完整的程序) :

#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <typeinfo>
#include <typeindex>
#include <type_traits>
#include <utility>
#include <tuple>

using namespace std;

OneOf 和我依赖的图书馆 (https://ciaranm.wordpress.com/2010/07/15/generic-lambda-visitors-or-writing-haskell-in-c0x-part-4/):

struct UnknownTypeForOneOf;

template <typename Want_, typename... Types_>
struct SelectOneOfType;

template <typename Want_>
struct SelectOneOfType<Want_>
{
    typedef UnknownTypeForOneOf Type;
};

template <typename Want_, typename Try_, typename... Rest_>
struct SelectOneOfType<Want_, Try_, Rest_...>
{
    typedef typename std::conditional<
        std::is_same<Want_, Try_>::value,
        Try_,
        typename SelectOneOfType<Want_, Rest_...>::Type
            >::type Type;
};

template <typename Type_>
struct ParameterTypes;

template <typename C_, typename R_, typename P_>
struct ParameterTypes<R_ (C_::*)(P_)>
{
    typedef P_ FirstParameterType;
    typedef R_ ReturnType;
};

template <typename C_, typename R_, typename P_>
struct ParameterTypes<R_ (C_::*)(P_) const>
{
    typedef P_ FirstParameterType;
    typedef R_ ReturnType;
};

template <typename Lambda_>
struct LambdaParameterTypes
{
    typedef typename ParameterTypes<decltype(&Lambda_::operator())>::FirstParameterType FirstParameterType;
    typedef typename ParameterTypes<decltype(&Lambda_::operator())>::ReturnType ReturnType;
};

template <typename Type_>
struct OneOfVisitorVisit
{
    virtual void visit(Type_ &) = 0;
};

template <typename... Types_>
struct OneOfVisitor :
    OneOfVisitorVisit<Types_>...
{
};

template <typename Visitor_, typename Underlying_, typename Result_, typename... Types_>
struct OneOfVisitorWrapperVisit;

template <typename Visitor_, typename Underlying_, typename Result_>
struct OneOfVisitorWrapperVisit<Visitor_, Underlying_, Result_> :
    Visitor_
{
    Underlying_ & underlying;
    std::function<Result_ ()> execute;

    OneOfVisitorWrapperVisit(Underlying_ & u) :
        underlying(u)
    {
    }
};

template <typename Visitor_, typename Underlying_, typename Result_, typename Type_, typename... Rest_>
struct OneOfVisitorWrapperVisit<Visitor_, Underlying_, Result_, Type_, Rest_...> :
    OneOfVisitorWrapperVisit<Visitor_, Underlying_, Result_, Rest_...>
{
    OneOfVisitorWrapperVisit(Underlying_ & u) :
        OneOfVisitorWrapperVisit<Visitor_, Underlying_, Result_, Rest_...>(u)
    {
    }

    Result_ visit_returning(Type_ & t)
    {
        return this->underlying.visit(t);
    }

    virtual void visit(Type_ & t)
    {
        this->execute = std::bind(&OneOfVisitorWrapperVisit::visit_returning, this, std::ref(t));
    }
};

template <typename Underlying_, typename Result_, typename... Types_>
struct OneOfVisitorWrapper :
    OneOfVisitorWrapperVisit<OneOfVisitor<Types_...>, Underlying_, Result_, Types_...>
{
    OneOfVisitorWrapper(Underlying_ & u) :
        OneOfVisitorWrapperVisit<OneOfVisitor<Types_...>, Underlying_, Result_, Types_...>(u)
    {
    }
};

template <typename... Types_>
struct OneOfValueBase
{
    virtual ~OneOfValueBase() = 0;

    virtual void accept(OneOfVisitor<Types_...> &) = 0;
    virtual void accept(OneOfVisitor<const Types_...> &) const = 0;
};

template <typename... Types_>
OneOfValueBase<Types_...>::~OneOfValueBase() = default;

template <typename Type_, typename... Types_>
struct OneOfValue :
    OneOfValueBase<Types_...>
{
    Type_ value;

    OneOfValue(const Type_ & type) :
        value(type)
    {
    }

    virtual void accept(OneOfVisitor<Types_...> & visitor)
    {
        static_cast<OneOfVisitorVisit<Type_> &>(visitor).visit(value);
    }

    virtual void accept(OneOfVisitor<const Types_...> & visitor) const
    {
        static_cast<OneOfVisitorVisit<const Type_> &>(visitor).visit(value);
    }
};

template <typename... Types_>
class OneOf
{
    private:
        std::unique_ptr<OneOfValueBase<Types_...> > _value;

    public:
        template <typename Type_>
        OneOf(const Type_ & value) :
            _value(new OneOfValue<typename SelectOneOfType<Type_, Types_...>::Type, Types_...>{value})
        {
        }

        OneOf(const OneOf & other) = delete;

        OneOf(OneOf && other) :
            _value(std::move(other._value))
        {
        }

        template <typename Type_>
        OneOf & operator= (const Type_ & value)
        {
            _value.reset(new OneOfValue<typename SelectOneOfType<Type_, Types_...>::Type, Types_...>{value});
            return *this;
        }

        OneOf & operator= (const OneOf & other) = delete;

        OneOf & operator= (OneOf && other)
        {
            _value = std::move(other._value);
            return *this;
        }

        OneOfValueBase<Types_...> & value()
        {
            return *_value;
        }

        const OneOfValueBase<Types_...> & value() const
        {
            return *_value;
        }
};

template <typename Visitor_, typename Result_, typename OneOf_>
struct OneOfVisitorWrapperTypeFinder;

template <typename Visitor_, typename Result_, typename... Types_>
struct OneOfVisitorWrapperTypeFinder<Visitor_, Result_, const OneOf<Types_...> &>
{
    typedef OneOfVisitorWrapper<Visitor_, Result_, const Types_...> Type;
};

template <typename Visitor_, typename Result_, typename... Types_>
struct OneOfVisitorWrapperTypeFinder<Visitor_, Result_, OneOf<Types_...> &>
{
    typedef OneOfVisitorWrapper<Visitor_, Result_, Types_...> Type;
};

template <typename Result_, typename OneOf_, typename Visitor_>
Result_
accept_returning(OneOf_ && one_of, Visitor_ && visitor)
{
    typename OneOfVisitorWrapperTypeFinder<Visitor_, Result_, OneOf_>::Type visitor_wrapper(visitor);
    one_of.value().accept(visitor_wrapper);
    return visitor_wrapper.execute();
}

template <typename OneOf_, typename Visitor_>
void accept(OneOf_ && one_of, Visitor_ && visitor)
{
    accept_returning<void>(one_of, visitor);
}

template <typename Result_, typename... Funcs_>
struct LambdaVisitor;

template <typename Result_>
struct LambdaVisitor<Result_>
{
    void visit(struct NotReallyAType);
};

template <typename Result_, typename Func_, typename... Rest_>
struct LambdaVisitor<Result_, Func_, Rest_...> :
    LambdaVisitor<Result_, Rest_...>
{
    Func_ & func;

    LambdaVisitor(Func_ & f, Rest_ & ... rest) :
        LambdaVisitor<Result_, Rest_...>(rest...),
        func(f)
    {
    }

    Result_ visit(typename LambdaParameterTypes<Func_>::FirstParameterType & v)
    {
        return func(v);
    }

    using LambdaVisitor<Result_, Rest_...>::visit;
};

template <typename... Funcs_>
struct AllReturnSame;

template <typename Func_>
struct AllReturnSame<Func_>
{
    enum { value = true };
};

template <typename A_, typename B_, typename... Funcs_>
struct AllReturnSame<A_, B_, Funcs_...>
{
    enum { value = std::is_same<typename LambdaParameterTypes<A_>::ReturnType, typename LambdaParameterTypes<B_>::ReturnType>::value &&
        AllReturnSame<B_, Funcs_...>::value };
};

template <typename...>
struct SeenSoFar
{
};

template <typename...>
struct ExtendSeenSoFar;

template <typename New_, typename... Current_>
struct ExtendSeenSoFar<New_, SeenSoFar<Current_...> >
{
    typedef SeenSoFar<Current_..., New_> Type;
};

template <typename...>
struct AlreadySeen;

template <typename Query_>
struct AlreadySeen<SeenSoFar<>, Query_>
{
    enum { value = false };
};

template <typename Query_, typename A_, typename... Rest_>
struct AlreadySeen<SeenSoFar<A_, Rest_...>, Query_>
{
    enum { value = std::is_same<Query_, A_>::value || AlreadySeen<SeenSoFar<Rest_...>, Query_>::value };
};

template <typename...>
struct OneOfDeduplicatorBuilder;

template <typename... Values_>
struct OneOfDeduplicatorBuilder<SeenSoFar<Values_...> >
{
    typedef OneOf<Values_...> Type;
};

template <typename SeenSoFar_, typename Next_, typename... Funcs_>
struct OneOfDeduplicatorBuilder<SeenSoFar_, Next_, Funcs_...>
{
    typedef typename std::conditional<
        AlreadySeen<SeenSoFar_, Next_>::value,
        typename OneOfDeduplicatorBuilder<SeenSoFar_, Funcs_...>::Type,
        typename OneOfDeduplicatorBuilder<typename ExtendSeenSoFar<Next_, SeenSoFar_>::Type, Funcs_...>::Type
            >::type Type;
};

template <typename... Funcs_>
struct OneOfDeduplicator
{
    typedef typename OneOfDeduplicatorBuilder<SeenSoFar<>, Funcs_...>::Type Type;
};

template <typename... Funcs_>
struct WhenReturnType;

template <typename FirstFunc_, typename... Funcs_>
struct WhenReturnType<FirstFunc_, Funcs_...>
{
    typedef typename std::conditional<
        AllReturnSame<FirstFunc_, Funcs_...>::value,
        typename LambdaParameterTypes<FirstFunc_>::ReturnType,
        typename OneOfDeduplicator<
            typename LambdaParameterTypes<FirstFunc_>::ReturnType,
            typename LambdaParameterTypes<Funcs_>::ReturnType ...>::Type
        >::type Type;
};

template <typename Val_, typename... Funcs_>
typename WhenReturnType<Funcs_...>::Type
when(Val_ && val, Funcs_ && ... funcs)
{
    LambdaVisitor<typename WhenReturnType<Funcs_...>::Type, Funcs_...> visitor(funcs...);
    return accept_returning<typename WhenReturnType<Funcs_...>::Type>(val, visitor);
}

这是应用程序特定的代码:

struct Base {
    Base(const string &i = "Base"):i(i){}
    virtual ~Base(){}

    string i;   
};

struct A : public Base {
    A():Base("A"){}
};

struct B : public Base {
    B():Base("B"){}
};

struct C : public Base {
    C():Base("C"){}
};

struct Node {

    template<typename ContainerObjectType>
    void castAndAdd(const shared_ptr<Base> &base, vector<ContainerObjectType> &container){
        //base case do nothing
    }

    template<typename ContainerObjectType, typename T>
    void castAndAdd(const shared_ptr<Base> &base, vector<ContainerObjectType> &container){
        if(typeid(*base) == typeid(T)){
            cout << "Adding: " << base->i << endl;
            container.push_back(std::static_pointer_cast<T>(base));
        }
    }

    template<typename ContainerObjectType, typename T, typename T2, typename ...V>
    void castAndAdd(const shared_ptr<Base> &base, vector<ContainerObjectType> &container){
        if(typeid(*base) == typeid(T)){
            cout << "Adding: " << base->i << endl;
            container.push_back(std::static_pointer_cast<T>(base));
        }else{
            castAndAdd<ContainerObjectType, T2, V...>(base, container);
        }
    }

    template<typename ...T> 
    vector<OneOf<shared_ptr<T>...>> components(){
        vector<OneOf<shared_ptr<T>...>> results;
        for(auto&& item : contents){
            castAndAdd<OneOf<shared_ptr<T>...>, T...>(item, results);
        }
        return results;
    }

    vector<shared_ptr<Base>> contents;
};


int main() {
    Node root;
    root.contents.push_back(make_shared<Base>());
    root.contents.push_back(make_shared<C>());
    root.contents.push_back(make_shared<B>());
    root.contents.push_back(make_shared<A>());
    root.contents.push_back(make_shared<C>());

    auto components = root.components<A, B>();

    for(auto&& component : components){
        when(component,
        [](const std::shared_ptr<B> &item){
            cout << "B found: " << item->i << std::endl;
        },
        [](const std::shared_ptr<C> &item){
            cout << "C found: " << item->i << std::endl;
        },
        [](const std::shared_ptr<A> &item){
            cout << "A found: " << item->i << std::endl;
        });
    }

    return 0;
}

关于c++ - 可变访客类型推导,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30600763/

相关文章:

c++ - 使用 RapidJSON 解析文档时跳过某些字段

html - golang html/template ExecuteTemplate errant byte 它来自哪里?

Java 运行时命令行进程格式不正确

c++ - 为什么我的节点 tempNode 不能显示正确的数据?

c++ - 我们需要序列化 ​​VAO 和 VBO

C++ 改变 const char 中的值

javascript - 使用隐藏状态而不是注释或自定义脚本标记来模板化 HTML

c++ - 在 Typedef 中转换模板参数

java - 谁能告诉我如何提高这段代码的运行时间?

java - 查找关联的程序以使用 Java 打开文件