7

I have this C++ class that one big complicated method compute that I would like to feed with a "compute kernel", a method of the same class. I figure I would do something along the lines of

class test {
int classVar_ = 42;

int compute_add(int a, int b)
{
   compute(int a, int b, this->add_())
}

int compute_mult(int a, int b)
{
   compute(int a, int b, this->mult_())
}


int compute_(int a, int b, "pass in add or multiply as f()")
{
   int c=0;
   // Some complex loops {
   c += f(a,b)
   // }
   return c;
}

int add_(int a, int b){a+b+classVar_;}
int multiply_(int a, int b){a*b+classVar_;}
...

}

but I'm not sure how I would pass in add or multiply. An alternative to this approach would be to pass in an ENUM of some sort to specify add() or multiply(), but I wanted to avoid a switch or if inside the loops.

What's best practice here?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 1
    You have a `member-function-pointers` tag. Isn't that a good place to start? – chris Jun 29 '12 at 10:38
  • @chris i guess he's confused about the syntax? – RedX Jun 29 '12 at 10:42
  • 1
    If the methods you want to pass are as in your sample, plain (static) functions & ordinary function pointers are enough. – Mat Jun 29 '12 at 10:43
  • @RedX, Not really a problem with Google, seeing as how it is a pretty common topic for source code. – chris Jun 29 '12 at 10:45

3 Answers3

5

As you suspected, passing a member function pointer is acceptable practice.

If you need to know the syntax, it is:

int compute_(int a, int b, int (test::*f)(int,int))
{
   int c=0;
   // Some complex loops {
   c += (this->*f)(a,b)
   // }
   return c;
}

Representing member functions using integers, and switching, introduces programmer overhead to keep things up to date when the list of available operations changes. So you don't want that unless there's some important reason in a particular case.

One alternative is to make compute even more general -- instead of taking a member function, write a function template that takes any callable type:

template <typename BinaryFunction>
int compute_(int a, int b, BinaryFunction f) {
    // body as before but `f(a,b)` instead of `(this->*f)(a,b)`
}

This more general template is great if someone wants to use it with some operator of their own invention, that isn't a member function of test. It's more difficult to use in the case of the member function, though, because someone needs to capture this. There are a few ways to do that -- a C++11 lambda, boost::bind, or writing out a functor longhand. For example:

template <typename BinaryFunction>
int compute_(int a, int b, BinaryFunction f) {
    // body as before with `f(a,b)`
}

int compute_(int a, int b, int (test::*f)(int,int))
{
    return compute_(a, b, bind_this(f, this));
}

Defining bind_this is a bit of a pain: it's like std::bind1st except that we'd like to work with a 3-arg functor whereas bind1st only takes a binary functor. boost::bind, and std::bind in C++11, are more flexible, and will handle the extra arguments. The following will do for this case, but doesn't work in general to bind 2-arg member functions:

struct bind_this {
    int (test::*f)(int,int);
    test *t;
    int operator(int a, int b) const {
        return (t->*f)(a,b);
    }
    bind_this(int (test::*f)(int,int), test *t) : f(f), t(t) {}
};

In C++11 you can just use a lambda:

int compute_(int a, int b, int (test::*f)(int,int))
{
    return compute_(a, b, [=](int c, int d){ return (this->*f)(c,d) });
}
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • How would you call `compute_(int a, int b, int (test::*f)(int,int))`? `compute_(1,2,this->add_)` yields `cannot initialize a parameter of type 'int (test::*)(int, int)' with an rvalue of type ''`. `compute_(1,2,&this->add_)` yields `cannot create a non-constant pointer to member function`. – Nico Schlömer Jun 29 '12 at 11:37
  • @Nico: try simply `compute_(1,2,add_)`. – Mat Jun 29 '12 at 11:40
  • @Mat Well, that's the same thing as `this->add_`, the extra qualifier only tells the compiler where to look for `add_`. – Nico Schlömer Jun 29 '12 at 11:44
  • GCC is more specific: `ISO C++ forbids taking the address of a bound member function to form a pointer to member function.` (This is for the case with `&`). I found http://stackoverflow.com/questions/2374847/passing-member-function-pointer-to-member-object-in-c to be a very similar question. Let me check this out. – Nico Schlömer Jun 29 '12 at 11:47
  • 2
    @Nico: I'd call it as `compute_(1,2,&test::add_)`, but I'm prepared to believe that Mat's suggestion is correct within the scope of class `test`. Evidently `this->_add` is *not* the same as `_add` when forming a pointer-to-member-function, even though it is the same when calling that member function. – Steve Jessop Jun 29 '12 at 12:20
  • @SteveJessop That did the trick! I wasn't aware of the implications of `this->`. Thanks a LOT. – Nico Schlömer Jun 29 '12 at 12:48
1

You have two alternatives :

  1. using pointer to member function
  2. using lambda functions

Example using pointer to member function :

#include <iostream>

class D
{
public:
  D(int v ) : classVar_(v){}
  int add_(int a, int b){return (a+b+classVar_);}
  int multiply_(int a, int b){return (a*b+classVar_);}
private:
  int classVar_;
};

class test {
public:

int compute_(int a, int b, D &d, int (D::*f)(int a, int b))
{
   int c=0;
   // Some complex loops {
   c += (d.*f)(a,b);
   // }
   return c;
}

};

int main()
{
  test test;
  D d(1);

  std::cout<<"add : " << test.compute_( 5, 4, d, &D::add_ ) << std::endl;
  std::cout<<"add : " << test.compute_( 5, 4, d, &D::multiply_ ) << std::endl;
}

Example using lambda :

#include <iostream>
#include <functional>

class D
{
public:
  D(int v ) : classVar_(v){}
  int add_(int a, int b){return (a+b+classVar_);}
  int multiply_(int a, int b){return (a*b+classVar_);}
private:
  int classVar_;
};

class test {
public:

int compute_(int a, int b, std::function< int(int,int) > f)
{
   int c=0;
   // Some complex loops {
   c += f(a,b);
   // }
   return c;
}

};

int main()
{
  test test;
  D d(1);

  std::cout<<"add : " << test.compute_( 5, 4, [&d](int a, int b){ return d.add_(a,b); } ) << std::endl;
  std::cout<<"add : " << test.compute_( 5, 4, [&d](int a, int b){ return d.multiply_(a,b); } ) << std::endl;
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273
1

Use pointers to functions.

int compute(int a, int b, int (test::*f) (int, int) )
{
   int c=0;
   // Some complex loops {
   c += (this->*f)(a,b)
   // }
   return c;
}
Gabi
  • 600
  • 2
  • 13