member function mit exakter Signatur binden



  • Hallo,

    mir ist kein besserer Name für den Titel eingefallen 😕
    Ich habe dieses Problem:

    #include <vector>
    #include <iostream>
    #include <functional>
    
    template<typename Signature>
    struct Publisher
    {
       using FunctionType = std::function<Signature>;
    
       std::vector<FunctionType> Subscribers_;
    
       void subscribe( Signature FreeFunc )
       {
          Subscribers_.emplace_back( FreeFunc );
       }
    
       template<typename ObjType,
                typename P1>
       void subscribe( ObjType* Obj, void (ObjType::*MemFun)( P1 ) )
       {
          Subscribers_.push_back( std::bind( MemFun, Obj,
                                  std::placeholders::_1 ) );
       }
    
       template<typename ObjType,
                typename P1,
                typename P2>
       void subscribe( ObjType* Obj, void (ObjType::*MemFun)( P1, P1 ) )
       {
          Subscribers_.push_back( std::bind( MemFun, Obj,
                                  std::placeholders::_1,
                                  std::placeholders::_2 ) );
       }
    
       template<typename ...Params>
       void operator()( Params&&... p )
       {
          for( const auto& Sub : Subscribers_ )	
          {
             Sub( std::forward<Params>( p )... );	
          }
       }
    };
    
    void func1( unsigned int )
    {
    }
    
    void func2( unsigned int& )
    {
    }
    
    void func3( const unsigned int& )
    {
    }
    
    struct Test
    {
       void func1( unsigned int )
       {
       }
    
       void func2( unsigned int& )
       {
       }
    
       void func3( const unsigned int& )
       {
       }
    };
    
    int main()
    {
       Publisher<void ( unsigned int& )> Pub;
    
    //   Pub.subscribe( &func1 ); // falsche Signatur -> Compiler Error
       Pub.subscribe( &func2 );
    //   Pub.subscribe( &func3 ); // falsche Signatur -> Compiler Error
    
       Test t;  
       Pub.subscribe( &t, &Test::func1 ); // falsche Signatur -> ok
       Pub.subscribe( &t, &Test::func2 );
       Pub.subscribe( &t, &Test::func3 ); // falsche Signatur -> ok
    
       unsigned int Arg = 1;
       Pub( Arg );
    }
    

    Hier der Code auf Ideone

    1. die Parameter P1 in der subscribe -Methode für member Funktionen verlieren ihre CV-Qualifikation. Damit lassen sich Test::func1 , Test::func2 und Test::func3 binden, obwohl nur die Signatur von Test::func2 passt.
    2. ich muss für member Funktionen die Platzhalter für die Argumente angeben und benennen. Das erfordert ein subscribe-Methoden für member Funktionen mit unterschiedlich vielen Parametern.

    Wie kann ich sicherstellen, dass die Member-Funktion in subscribe exakt der vorgegebenen Signatur entspricht?
    Und gibt´s für 2) eine elegante Lösung?



  • So...
    Habe das etwas umgestellt und eine halbwegs zufrieden stellende Lösung gefunden. Dazu musste ich den template Parameter des Publisher ändern, der sieht jetzt leider nicht mehr wie eine Funktionssignatur aus.

    #include <vector>
    #include <functional>
    
    namespace
    {
       // Catch-all für nicht unterstützte Anzahl von Aufrufparametern
       template<typename RetType, unsigned int ParamCount>
       struct SubscriberFactory;
    
       // Spezialisierung für 0 Parameter
       template<typename RetType>
       struct SubscriberFactory<RetType,0> 
       {
          template<typename ObjType, typename MemFunType>
          static RetType create_subscriber( ObjType* Obj, MemFunType Fun )
          {
             return std::bind( Fun, Obj );
          }
       }
       // Spezialisierung für 1 Parameter
       template<typename RetType>
       struct SubscriberFactory<RetType,1> 
       {
          template<typename ObjType, typename MemFunType>
          static RetType create_subscriber( ObjType* Obj, MemFunType Fun )
          {
             return std::bind( Fun, Obj,
                               std::placeholders::_1 );
          }
       }
       // usw...
    }
    
    template<typename ...Params>
    class Publisher
    {
       using Signature    = void( Params... );
       using FunctionType = std::function<Signature>;
    
       std::vector<FunctionType> Subscribers_;
    
    public:
       void subscribe( Signature FreeFunc )
       {
          Subscribers_.emplace_back( FreeFunc );
       }
    
       template<typename ObjType>
       void subscribe( ObjType* Obj, void (ObjType::*MemFun)( Params... ) )
       {
          SubscriberFactory<FuncType, sizeof...( Params )> Factory;
          Subscribers_.push_back( Factory.template create_subscriber( Obj, MemFun ) );
       }
    
       void operator()( Params&&... p )
       {
          for( const auto& Sub : Subscribers_ )	
          {
             Sub( std::forward<Params>( p )... );	
          }
       }
    };
    


  • So?

    template <typename SigT>
        struct Publisher;
    
    template <typename SigT>
        struct PublisherBase; // am besten in einem detail-Namespace verstecken
    template <typename... Ts>
        struct PublisherBase<void(Ts...)>
    {
    private:
        using Sig = R(Ts...);
    
    public:
        void subscribe(void (*freeFunc)(Ts...))
        {
            static_cast<Publisher<Sig>*>(this)->doSubscribe(freeFunc);
        }
        template <class C>
            void subscribe(C* inst, void (C::*memberFunc)(Ts...))
        {
            static_cast<Publisher<Sig>*>(this)->doSubscribe([inst, memberFunc](Ts&&... args)
            {
                (inst->*memberFunc)(std::forward<Ts>(args)...);
            });
        }
        template <class C>
            void subscribe(const C* inst, void (C::*memberFunc)(Ts...) const)
        {
            static_cast<Publisher<Sig>*>(this)->doSubscribe([inst, memberFunc](Ts&&... args)
            {
                (inst->*memberFunc)(std::forward<Ts>(args)...);
            });
        }
    
        void operator ()(Ts... args) const
        {
            for (auto& func : static_cast<const Publisher<Sig>*>(this)->subscribers_)
                func(std::forward<Ts>(args)...);
        }
    };
    
    template <typename SigT>
        struct Publisher : PublisherBase<SigT>
    {
        friend PublisherBase<SigT>;
    
    private:
        std::vector<std::function<SigT>> subscribers_;
        void doSubscribe(std::function<SigT> func)
        {
            subscribers_.push_back(std::move(func));
        }
    };
    


  • Wenn ich es mir recht überlege, kannst du sogar auf die Basisklasse verzichten und gleich Publisher<> selbst spezialisieren.

    template <typename SigT>
        struct Publisher;
    template <typename... Ts>
        struct Publisher<void(Ts...)>
    {
    private:
        std::vector<std::function<void(Ts...)>> subscribers_;
    
    public:
        void subscribe(void (*freeFunc)(Ts...))
        {
            subscribers_.push_back(freeFunc);
        }
        template <class C>
            void subscribe(C* inst, void (C::*memberFunc)(Ts...))
        {
            subscribers_.push_back([inst, memberFunc](Ts&&... args)
            {
                (inst->*memberFunc)(std::forward<Ts>(args)...);
            });
        }
        template <class C>
            void subscribe(const C* inst, void (C::*memberFunc)(Ts...) const)
        {
            subscribers_.push_back([inst, memberFunc](Ts&&... args)
            {
                (inst->*memberFunc)(std::forward<Ts>(args)...);
            });
        }
    
        void operator ()(Ts... args) const
        {
            for (auto& func : subscribers_)
                func(std::forward<Ts>(args)...);
        }
    };
    


  • Warum kommst du ohne std::placeholder aus?

    Edit:
    Hab´s verstanden, glaube ich. Ist ne schicke Lösung. Gefällt mir 👍



  • DocShoe schrieb:

    Warum kommst du ohne std::placeholder aus?

    Weil ich Lambdafunktionen statt std::bind() benutze.



  • Jau, hat nen Moment gedauert bis ich´s gesehen habe.


Log in to reply