C++ - is it possible to extract class and argument types from a member function type in a template?

前端 未结 4 1690
孤独总比滥情好
孤独总比滥情好 2021-02-15 18:19

I would like to wrap member functions that conform to the type \'void (ClassType::Function)(ArgType)\' with a templated class. Later, I want to pass an instance of ClassType to

相关标签:
4条回答
  • 2021-02-15 18:31
    struct MyClass
    {
        MyClass& Move(MyClass& m) { return *this; }
    };
    
    typedef MyClass& (MyClass::*MethodT) (MyClass&);
    
    template< typename T >
    struct ExtractType : std::false_type
    {
    };
    
    template< typename R, typename C, typename A >
    struct ExtractType< R (C::*)(A) >
    {
        typedef C type;
    };
    
    static_assert( std::is_same< ExtractType< MethodT >::type, MyClass >::value, "oops" );
    

    It appears to work in my version of gcc 4.8.
    It works like I mentioned in the comment, its a "back pattern matching" that the compiler does during specialization checks. This is very powerful.
    So you see, we specified some kind of pattern that if the type T respects, it will be decomposed by the compiler into the three subtypes that composes it: R, C, A. Which is return type, class type and argument.

    However you can see that it works with one argument. How to do when we have an undefined number of arguments ?
    Maybe a list of checker classes, or use variadic templates ?

    And frankly in all honesty, I am not even sure this will work with void. I think void is always impossible to place in template, therefore it will result in many versions of this ExtractType class to support all combinations of possible declarations. Or so it seems to me.

    EDIT:

    Ok so I'm giving this away completely randomly, but it seems in C++11 it works much better than I have expected, this is ok on gcc 4.8:

    struct MyClass
    {
    };
    
    typedef int (MyClass::*MethodT) (bool);
    typedef void (MyClass::*VV) ();
    typedef void (MyClass::*IL) (int, long);
    
    template< typename T >
    struct ExtractType : std::false_type
    {
    };
    
    template< typename R, typename C, class...A >
    struct ExtractType< R (C::*)(A...) >
    {
        typedef C type;
        typedef R returntype;
    };
    
    static_assert( std::is_same< ExtractType< MethodT >::type, MyClass >::value, "oops" );
    static_assert( std::is_same< ExtractType< VV >::type, MyClass >::value, "oops" );
    static_assert( std::is_same< ExtractType< IL >::type, MyClass >::value, "oops" );
    
    static_assert( std::is_same< ExtractType< MethodT >::returntype, int >::value, "oops" );
    static_assert( std::is_same< ExtractType< VV >::returntype, void >::value, "oops" );
    static_assert( std::is_same< ExtractType< IL >::returntype, void >::value, "oops" );
    

    The crazy part being that it doesn't mind void in the return type. Of course its C++11 however.

    0 讨论(0)
  • 2021-02-15 18:39

    This is a poor re-implementation of ::std::mem_fn + ::std::bind, which are C++11 constructs. Here is how you might do this using those:

    #include <functional>
    
    int main() {
       Foo foo;
       auto wrapper = ::std::bind(::std::mem_fn(&Foo::set), ::std::ref(foo), _1);
       wrapper(5); // Calls foo.set(5)
    }
    

    But, of course, you want a C++03 solution. Using Boost can get you this in C++03. I also believe that something like this is possible in C++03 with TR1. You can tell if you have that by seeing if #include <tr1/functional> works. If you have TR1 those show up in the ::std::tr1 namespace.

    Now, there is one way in which it isn't. You've made the function pointer itself part of the type signature of the class. That's sort of an odd thing to do, but certainly possible as you already know. Being able to determine the ClassType and ArgType values at compile time is tricky though. You can do it with template function argument matching, but not usefully because C++03 doesn't have auto.

    0 讨论(0)
  • 2021-02-15 18:40

    In C++11 you might use lambdas, like:

    template <typename X, typename ARG>
    std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG))
    {
        return [=](X *x, ARG arg) {
           (x->*mfp)(arg);
        };
    }
    

    With VisualC++ (at least as recent as VS2013), use capture by value [=] when capturing member function pointers (or experience crashes).

    Playground:

    #include <iostream>
    #include <functional>
    
    struct A {
        virtual void a(int i) { std::cout << "A: " << i << std::endl; }
    };
    
    struct B {
        virtual void b(int i) { std::cout << "B: " << i << std::endl; }
    };
    
    template <typename X, typename ARG>
    std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG)) {
        return [=](X *x, ARG arg) { (x->*mfp)(arg); };
    }
    
    int main()
    {
        auto g = wrapper(&B::b);
        B b;
        g(&b, 3);
        auto h = wrapper(&A::a);
        A a;
        h(&a, 4);
        return 0;
    }
    
    0 讨论(0)
  • 2021-02-15 18:49

    Reading over what you did made me think of a few options:

    1) Wrap the instantiation in inheritance. This moves the scary stuff to your definition.

    class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo
    {
    public:
        FooWrapper() : Wrapper(this){}
    };
    

    Your logic code would looks like this:

      FooWrapper fooWrapper;
      fooWrapper.do_something(1.0);
      std::cout << fooWrapper.get() << std::endl;
    

    Which means you didn't eliminate the double template arguments, you just moved them.

    2) There is a more generic way to wrap it, at one level:

      template<typename argType1, class classType1>
    class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1
    {
    public:
        FooWrapper2()
            : classType1(),
              Wrapper<argType1, classType1, &classType1::set>(this)
        {
    
        }
    };
    

    This way has the draw-back of more complicated looking logic, but you don't have to define a new wrapper every time, just a new wrapper for every signature:

      FooWrapper2<double, Foo> fooWrapper2;
      fooWrapper2.do_something(1.0);
      std::cout << fooWrapper2.get() << std::endl;
    

    3) Keeping in line with the template idea, you can wrap the wrapper:

      template<typename argType1>
    class FooWrapper3 : public FooWrapper2<argType1, Foo>
    {
    public:
        FooWrapper3()
        {
    
        }
    };
    

    The logic code from this looks a bit better, but you are stuck having to resubclass for each type you are wrapping (with specific code, instead of just using the template):

      FooWrapper3<double> fooWrapper3;
      fooWrapper3.do_something(1.0);
      std::cout << fooWrapper3.get() << std::endl;
    

    4) This option scraps the base wrapper class and uses an interface. Just pass the interfaces around as you would the wrappers and you can perform most actions.

    template <typename ArgType>  
    class Do_something {  
     public:  
    
      virtual void do_something(ArgType value) = 0;  
    
    };  
    
    template<typename ArgType>  
    class FooWrapper4 : public Foo, public Do_something<ArgType>  
    {  
    public:  
        virtual void do_something(ArgType value)  
        {  
            set(1.0);  
        }  
    };  
    

    The test program I played with:

    class Foo {
     public:
      Foo() : f_(0.0) {}
      void set(double v) { f_ = v * 2.1; }
      double get() { return f_; }
     private:
      double f_;
    };
    
    
    template <typename ArgType, typename ClassType, void (ClassType::*Method)(ArgType)>
    class Wrapper {
     public:
      explicit Wrapper(ClassType *cls) : cls_(cls) {}
    
      void do_something(ArgType value) {
        (cls_->*Method)(value);
      }
    
     private:
      ClassType *cls_;
    };
    
    
    class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo
    {
    public:
        FooWrapper() : Wrapper(this){}
    };
    
    
    template<typename argType1, class classType1>
    class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1
    {
    public:
        FooWrapper2()
            : classType1(),
              Wrapper<argType1, classType1, &classType1::set>(this)
        {
    
        }
    };
    
    template<typename argType1>
    class FooWrapper3 : public FooWrapper2<argType1, Foo>
    {
    public:
        FooWrapper3()
        {
    
        }
    };
    
    template <typename ArgType>
    class Do_something {
     public:
    
      virtual void do_something(ArgType value) = 0;
    
    };
    
    template<typename ArgType>
    class FooWrapper4 : public Foo, public Do_something<ArgType>
    {
    public:
        virtual void do_something(ArgType value)
        {
            set(1.0);
        }
    };
    
    #include <iostream>
    int main(int argc, char ** argv) {
      Foo foo;
      Wrapper<double, Foo, &Foo::set> wrapper(&foo);
    
      wrapper.do_something(1.0);
      std::cout << foo.get() << std::endl;
    
      FooWrapper fooWrapper;
      fooWrapper.do_something(1.0);
      std::cout << fooWrapper.get() << std::endl;
      // outputs "2.1"
    
      FooWrapper2<double, Foo> fooWrapper2;
      fooWrapper2.do_something(1.0);
      std::cout << fooWrapper2.get() << std::endl;
    
      FooWrapper3<double> fooWrapper3;
      fooWrapper3.do_something(1.0);
      std::cout << fooWrapper3.get() << std::endl;
    
      FooWrapper4<double> fooWrapper4;
      fooWrapper4.do_something(1.0);
      std::cout << fooWrapper4.get() << std::endl;
    
      return 0;
    }
    
    0 讨论(0)
提交回复
热议问题