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

4

8

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
Loggins answered 9/2, 2013 at 1:15 Comment(1)
I'm pretty sure it will be possible using a class specialization. because the deduction works in reverse, you could explode the method declaration type into 3 template types, from the base template declaration which has only one. And poof, extracted. I'm going to try that in gcc right away.Rauch
R
5
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.

Rauch answered 6/3, 2015 at 5:13 Comment(0)
A
3

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;
}
Acadia answered 4/4, 2014 at 20:54 Comment(0)
F
1

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.

Filide answered 9/2, 2013 at 1:59 Comment(3)
Thanks, I'll take a look at TR1 and/or Boost's variations. The reason I made the function pointer part of the type signature is because I want to extend this to match functions of other types, and have the correct specialisation match automatically. However now I think about it I wonder if this would be possible with argument matching on the function type anyway - but I'd still need to specify ClassType and ArgType as template parameters so that the argument type can be specified correctly.Loggins
@meowsqueak: I've thought about it a bit, and as long as you have the pointer value as a template argument, you cannot solve your problem in any way with function argument matching. The problem is that the template argument has to be a constant expression. And function arguments are not constant expressions. So while you can use template function argument deduction to pull the argument and class type out of a random pointer value, you cannot then use that pointer value as one of the template arguments for the return type. If that makes any sense at all.Filide
I think that makes sense. I've changed my approach now - instead of using the member-function pointer as a template parameter, I pass it as a normal argument. Then I have a small set of templates to deal with each signature I need to wrap. This means that the wrapped function's signature (including the name) isn't part of the type any more. So far it's working well.Loggins
T
1

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;
}
Troche answered 9/2, 2013 at 2:39 Comment(6)
Dear People of the Future, I can not seem to get the formatting to work correctly on one of the code segments, my apologizes .Troche
Thanks for replying. Regarding point 1, I need to extend this to wrap over 300 member functions (part of a configuration interface from a third party), knowing that there are only about six different Method signatures. Wrapping in inheritance for each one will involve writing 300 separate classes, unless I template them (which doesn't gain me anything in the end).Loggins
@meowsqueak, since that is the case, method 2 should give you what you are looking for. You should only have to define 6 of those.Troche
Based on what I said, Method 2 looks suitable, however I was mistaken in what I meant by "signature" - I realise now that the method name is part of the signature, and that changes too (set_foo, set_bar, etc). So I actually have 300 unique signatures, which is unfortunate. I may have to rethink that interface first.Loggins
If the function names start changing, I don't know a good template way to do that. If you have a list of all your function calls you can write a reg-ex to generate the code for you, but that's not as fun as the template stuff.Troche
I've changed my approach now - I've moved the function type out of the template parameter list; it's now a normal argument. This allows me to write distinct specialisations for each 'kind' of signature I need to wrap. So far it's working well (although I had problems with overloading not working on return types, sigh). Thanks for your help.Loggins

© 2022 - 2024 — McMap. All rights reserved.