0

I have a scene graph which contains nodes. Each node has a transform and several components. I have a 2d scene editor and am currently doing the following to select a component to edit:

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

Then in my editor code I have the following

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

I wanted to utilize the visitor pattern, but I don't want to seal my class to outsiders by creating a "Visitor" which has to be modified for every potential type of component. Thankfully I've found a cool conceptual work-around.

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

If I could modify my Node::components method to return

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

I could write something like this:

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

My question is, I want to write something like this:

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

And get back:

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

This has two issues I don't really know the syntax or methods to solve:

1) I need to be able to convert Sprite, Grid, Emitter in:

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

Into

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

2) I need to be able to run typeid against each item in the parameter pack (...ComponentList) to ensure it actually matches the underlying types I am looking for when iterating over my component list inside Node in order to push_back only the desired elements into the matchingComponents vector.

If you look at how I am currently checking against a single type it's pretty simple, but what I really need is something like:

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

TypeMatchesAnyOf and TypeMatched::type are the two components to this that I could see being integral.

M2tM
  • 4,415
  • 1
  • 34
  • 43
  • 2
    Those line lengths, though :) – isanae Jun 02 '15 at 15:51
  • Not quite sure what the -1 was for. If it was line length, sorry I guess? Did I not state my question clearly? – M2tM Jun 02 '15 at 16:40
  • 2
    Wasn't me, although I think it would be better to simplify your question. You have a lot of code in there that I think is unnecessary. Can you get rid of all the specific stuff you have and come up with a simplified version? – isanae Jun 02 '15 at 16:54
  • Yeah, no sweat, I laughed at your comment and didn't really figure it was you anyway, was just curious for feedback on why my question might not be good. Sure, I'll work on cutting it down a bit tonight. I may end up answering my own question if I can, but if not, I'll at least simplify. – M2tM Jun 02 '15 at 17:08
  • I found the following and will likely be diving into an implementation tonight with these additional tools: http://stackoverflow.com/questions/14261183/how-to-make-generic-computations-over-heterogeneous-argument-packs-of-a-variadic – M2tM Jun 02 '15 at 17:17
  • Answer below! :) It was a fun puzzle to solve. The answer below also includes a bare-bones simplified version of my problem. – M2tM Jun 02 '15 at 23:14

1 Answers1

1

After quite a bit of experimentation I was able to solve my issue. Essentially what I'm doing here is filtering a vector of objects based on variadic template parameters. This was a challenge mixing runtime and compile time type checking.

Hope this helps someone else as I believe it is a useful pattern for filtering components by type and applying a visitor without baking any knowledge of the visitor into the node class. This essentially solves the issue of the visitor pattern requiring a statically maintained double dispatch class.

https://ideone.com/19gTnw

Relevant includes/using std (do not do using std in a header, I only include it here because I want to include all of the ideone link so you could copy/paste each section of this answer to create the full program):

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

using namespace std;

The OneOf and where library I'm leaning on (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);
}

Here is the application specific code:

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;
}
M2tM
  • 4,415
  • 1
  • 34
  • 43