tags:

views:

175

answers:

5

Hi,

I am trying to write a code that calls a class method given as template parameter. To simplify, you can suppose the method has a single parameter (of an arbitraty type) and returns void. The goal is to avoid boilerplate in the calling site by not typing the parameter type. Here is a code sample:

template <class Method> class WrapMethod {
  public:
   template <class Object>
   Param* getParam() { return &param_; }
   Run(Object* obj) { (object->*method_)(param_); }
  private:
    typedef typename boost::mpl::at_c<boost::function_types::parameter_types<Method>, 1>::type Param;
    Method method_;
    Param param_
};

Now, in the calling site, I can use the method without ever writing the type of the parameter.

Foo foo;
WrapMethod<BOOST_TYPEOF(&Foo::Bar)> foo_bar;
foo_bar.GetParam()->FillWithSomething();
foo_bar.Run(foo);

So, this code works, and is almost what I want. The only problem is that I want to get rid of the BOOST_TYPEOF macro call in the calling site. I would like to be able to write something like WrapMethod<Foo::Bar> foo_bar instead of WrapMethod<BOOST_TYPEOF(&Foo::Bar)> foo_bar.

I suspect this is not possible, since there is no way of referring to a method signature other than using the method signature itself (which is a variable for WrapMethod, and something pretty large to type at the calling site) or getting the method pointer and then doing typeof.

Any hints on how to fix these or different approaches on how to avoid typing the parameter type in the calling site are appreciated.

Just to clarify my needs: the solution must not have the typename Param in the calling site. Also, it cannot call FillWithSomething from inside WrapMethod (or similar). Because that method name can change from Param type to Param type, it needs to live in the calling site. The solution I gave satisfies both these constraints, but needs the ugly BOOST_TYPEOF in the calling site (using it inside WrapMethod or other indirection would be fine since that is code my api users won't see as long as it is correct).

Response:

As far as I can say, there is no possible solution. This boil down to the fact that is impossible to write something like WrapMethod<&Foo::Bar>, if the signature of Bar is not known in advance, even though only the cardinality is necessary. More generally, you can't have template parameters that take values (not types) if the type is not fixed. For example, it is impossible to write something like typeof_literal<0>::type which evalutes to int and typeof_literal<&Foo::Bar>::type, which would evaluate to void (Foo*::)(Param) in my example. Notice that neither BOOST_TYPEOF or decltype would help because they need to live in the caling site and can't be buried deeper in the code. The legitimate but invalid syntax below would solve the problem:

template <template<class T> T value> struct typeof_literal {
  typedef decltype(T) type; 
};

In C++0x, as pointed in the selected response (and in others using BOOST_AUTO), one can use the auto keyword to achieve the same goal in a different way:

template <class T> WrapMethod<T> GetWrapMethod(T) { return WrapMethod<T>(); }
auto foo_bar = GetWrapMethod(&Foo::Bar);

Thanks.

A: 

Have you considered using method templates?

 template <typename T> void method(T & param)
 {
   //body
 }

Now the compiler is able to implicitly determine parameter type

           int i;
           bool b;

           method(i);
           method(b);

Or you can provide type explicitly

           method<int>(i);

You can provide specializations for different data types

           template <> void method<int>(int param)
           {
               //body
           }
doc
Because he wants to store them as a data member.
DeadMG
How are member templates supposed to help with the declaration of `WrapMethod`?
Georg Fritzsche
+1  A: 

Write it as:

template <typename Object, typename Param, void (Object::*F)(Param)> 
class WrapMethod { 
public: 
   Param* getParam() { return &param_; } 
   void Run(Object* obj) { (obj->*F)(param_); } 

private: 
    Param param_;
}; 

and

Foo foo; 
WrapMethod<Foo, Param, &Foo::Bar> foo_bar;       
foo_bar.getParam()->FillWithSomething();
foo_bar.Run(foo);

EDIT: Showing a template function allowing to do the same thing without any special template wrappers:

template <typename Foo, typename Param>
void call(Foo& obj, void (Foo::*f)(Param))
{
    Param param;
    param.FillWithSomthing();
    obj.*f(param);
}

and use it as:

Foo foo;
call(foo, &Foo::Bar);

2nd EDIT: Modifying the template function to take the initialization function as a parameter as well:

template <typename Foo, typename Param>
void call(Foo& obj, void (Foo::*f)(Param), void (Param::*init)())
{
    Param param;
    param.*init();
    obj.*f(param);
}

and use it as:

Foo foo;
call(foo, &Foo::Bar, &Param::FillWithSomething);
hkaiser
Davi
You did not say that in your original question. Anyways, it's not possible to do otherwise with todays C++, either use typeof, or use the solution I provided. But, if your were willing to change your design to utilize a template function (instead of a class template), it can be done. I edited my answer to illustrate what I mean.
hkaiser
Ah, thanks for the second option, but it still doesn't help. The param.FillWithSomething() call needs to live in the calling site, since this method can be different for different Param parameters.I am thinking this is not possible either with c++0X (although with auto I could use a template function).
Davi
Why not extend the scheme I proposed to the initialization function as well? I extended my answer again to demonstrate what I mean.
hkaiser
Unfortunately the parameter type name Param, still shows up in the calling site.
Davi
A: 

When you are already allowing BOOST_TYEPOF(), consider using BOOST_AUTO() with an object generator function to allow type deduction:

template<class Method> WrapMethod<Method> makeWrapMethod(Method mfp) {
    return WrapMethod<Method>(mfp);
}

BOOST_AUTO(foo_bar, makeWrapMethod(&Foo::Bar));
Georg Fritzsche
That works, but is no better than the solution I posted. BOOST_AUTO is calling BOOST_TYPEOF under the hoods. I want to avoid the macro call at the calling site. Doing dirty tricks inside WrapMethod is fine, but the calling site needs to be readable.
Davi
@Davi: If you don't like macros it doesn't get better pre-C++0x (in C++0x you have `auto` and `decltype`). There is no way to have non-type template arguments with an arbitrary type for class templates, thus there is no way to only pass the member function pointer... Whatever tricks the helper function may do, it leaves you with the problem of specifying the type for the variable.
Georg Fritzsche
If macros are to be avoided and you can't use the mentioned C++0x features in your code, the most readable alternative would be the Boost.Function style of `WrapMethod<void (int)>`.
Georg Fritzsche
A solution that uses auto or decltype or arbitrary macros inside WrapMethod would be welcome. I just need to avoid them in the calling site.
Davi
@Davi: With the requirements given, you can't. Either pass the return and argument types to `WrapMethod` or use `auto`/`decltype` or their macro equivalents.
Georg Fritzsche
Davi
@Davi: Yes, its sometimes annoying and one of the reasons for mechanisms like `decltype`. Pre-C++0x we have to choose the least clumsy compromise :)
Georg Fritzsche
@georg-frizsche But even in the C++0x I am still stuck with using decltype in the calling site right? I want to move the complexity inside WrapMethod, and C++0x won't help with any of this.I could however use the auto solution that you suggested. It is a different approch, but would accomplish what I need.
Davi
@Davi: In C++0x the user can hide the complexity using `auto wm = makeWrapMethod(` - which is what `BOOST_AUTO` emulates.
Georg Fritzsche
+1  A: 

If your compiler supports decltype, use decltype:

WrapMethod<decltype(&Foo::Bar)> foo_bar;

EDIT: or, if you really want to save typing and have a C++0x compliant compiler:

template <class T> WrapMethod<T> GetWrapMethod(T) { return WrapMethod<T>(); }
auto foo_bar= GetWrapMethod(&Foo::Bar);

EDIT2: Although, really, if you want it to look pretty you either have to expose users to the intricacies of the C++ language or wrap it yourself in a preprocessor macro:

#define WrapMethodBlah(func) WrapMethod<decltype(func)>
MSN
That works, but BOOST_TYPEOF an decltype are the same (almost). Using them are fine inside WrapMethod, but not in the calling site.
Davi
Right. Your edited answer has pretty much all the options, I guess. So, with C++03 I am stuck and with C++0X I could use decltype instead of BOOT_TYPEOF or I could use the auto solution, which is pretty nice.
Davi
A: 

Okay let's have a go at this.

First of all, note that template parameter deduction is available (as noted in a couple of answers) with functions.

So, here is an implementation (sort of):

// WARNING: no virtual destructor, memory leaks, etc...

struct Foo
{
  void func(int e) { std::cout << e << std::endl; }
};

template <class Object>
struct Wrapper
{
  virtual void Run(Object& o) = 0;
};

template <class Object, class Param>
struct Wrap: Wrapper<Object>
{
  typedef void (Object::*member_function)(Param);

  Wrap(member_function func, Param param): mFunction(func), mParam(param) {}

  member_function mFunction;
  Param mParam;

  virtual void Run(Object& o) { (o.*mFunction)(mParam); }
};

template <class Object, class Param>
Wrap<Object,Param>* makeWrapper(void (Object::*func)(Param), Param p = Param())
{
  return new Wrap<Object,Param>(func, p);
}


int main(int argc, char* argv[])
{
  Foo foo;
  Wrap<Foo,int>* fooW = makeWrapper(&Foo::func);
  fooW->mParam = 1;
  fooW->Run(foo);

  Wrapper<Foo>* fooW2 = makeWrapper(&Foo::func, 1);
  fooW2->Run(foo);
  return 0;
}

I think that using a base class is the native C++ way of hiding information by type erasure.

Matthieu M.
But that would restrict the parameter to be either bound at creation time of the wrapper or to use some virtual base class... :)
Georg Fritzsche
Yes, I agree, it's an alternative approach. I would like to know what the use case is though, because here we're lacking data. I feel that a base class is going to be necessary at one point or another, because if not for polymorphism, there isn't much point in wrapping in an object instead of a traditional method template.
Matthieu M.
Yeah, interesting approach, but as Georg says, you either need to type "int" in the calling site (first example) or you need to already have an anonymous instance of it (example 2). I simply can't do my FillWithSomething() example work.
Davi