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

。_饼干妹妹 提交于 2019-12-09 10:53:14

问题


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 an instance of this template and have it invoke the wrapped method:

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

#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;
  // outputs "2.1"
  return 0;
}

Notice in the instantiation of Wrapper<> that "Foo" is specified twice - it looks redundant here.

So what I'd like to know is whether it's possible to avoid the template parameter ClassType. For instance, if it is possible to imply or extract it from the member function pointer parameter, then it wouldn't need to be explicitly specified in the instantiation of Wrapper<>.

In a similar manner, it would be useful to avoid explicitly specifying ArgType also, as (perhaps) it can be determined from Foo::set?

Is this possible in C++? Perhaps something along these (entirely fantastical) lines:

template <void (ClassType::*Method)(ArgType)>
class Wrapper2 {
 public:
  explicit Wrapper(Method::ClassType *cls) : cls_(cls) {}

  void do_something(Method::ArgType value) {
    (cls_->*Method)(value);
  }

 private:
  Method::ClassType *cls_;
};

// ...

int main() {
  Foo foo;
  Wrapper<&Foo::set> wrapper(&foo);
  // ...
}

Or, perhaps there's another level of template magic that can be invoked that would do something along these lines:

Wrapper<Magic<&Foo::set> > wrapper(&foo);

I'm interested to know what mechanisms might be available, if any.

I'm using C++03 as a requirement, not C++11, but also interested to know what C++11 might offer.

EDIT: more info - I intend to use this mechanism to wrap ~300 member functions (all belonging to ClassType, or a set of very similar classes), but there will only be around six or so signatures to consider:

  • void (ClassType::Function)(ArgType) - where ArgType is 'floating'
  • void (ClassType::Function)(ArgType) - where ArgType is 'integral'
  • void (ClassType::Function)(bool)
  • void (ClassType::Function)(IndexType, ArgType) - the above three with an extra 'index' argument

The member functions are 'setter' functions for what I call "properties" in a large configuration 'collection' class, for example (rather than the simple Foo above):

class MyPropertyCollection {
 public:
  void set_oink(double value) { oink_ = value; }
  void set_bar(int value) { bar_ = value; }
  void set_squee(bool value) { squee_ = value; }
 private:
  double oink_;
  int bar_;
  bool squee_;
};

// elsewhere
WrapperCollection wrapper_collection;  // a simple set of wrapper objects, accessed by id
MyPropertyCollection property_collection;
wrapper_collection.add(PROPERTY_OINK_ID, new Wrapper<double, MyPropertySet, &MyPropertySet::set_oink>(&property_collection);
wrapper_collection.add(PROPERTY_BAR_ID, new Wrapper<int, MyPropertySet, &MyPropertySet::set_bar>(&property_collection);
wrapper_collection.add(PROPERTY_SQUEE_ID, new Wrapper<bool, MyPropertySet, &MyPropertySet::set_squee>(&property_collection);
// +300 more

回答1:


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.




回答2:


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



回答3:


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



回答4:


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.



来源:https://stackoverflow.com/questions/14783876/c-is-it-possible-to-extract-class-and-argument-types-from-a-member-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!