0

I'm trying to move some global functions inside a class. The current code looks like this:

struct S{};

void f(S* s, int x, double d){}
void g(S* s, int x, double d){}
/*
...
...*/
void z(S* s, int x, double d){}

int main()
{
    function<void(S*, int, double)> fp;
    
    //switch(something)
    //case 1:
        fp = f;
    //case 2:
        fp = g;
    //case n:
        fp = z;
}

Suppose I wanted to update the code above to something like (this code bellow doesn't compile):

#include <iostream>
#include <functional>

using namespace std;

struct S
{
    void f(int x, double d){}
    void g(int x, double d){}
    /*
    ...
    ...*/
    void z(int x, double d){}    

    void method_that_has_some_logic_to_use_the_others_above(int x, double d)
    {
        function<void(int, double)> fp; // How do I have to declare here?
        //switch(something)
        //case 1:
        fp = f; // How can achieve something like this using lambdas here?
    //case 2:
        fp = g;
    //case n:
        fp = z;

        fp(x, d);
    }

};


int main()
{
    S s;
    s.method_that_has_some_logic_to_use_the_others_above(10, 5.0);
}

I've seen some solutions using std::bind but read somewhere to avoid using it and prefer lambdas. I'm using C++17, but I have little experience with lambdas and wasn't able to figure out how to solve it with the examples I've found in other answers.

vmp
  • 2,370
  • 1
  • 13
  • 17
  • Do you need a `std::function` instead of a pointer? (Maybe, but your example is simple enough that a function pointer could be used in the first scenario, and a pointer-to-member-function in the second.) – JaMiT Apr 15 '22 at 04:45
  • I'm trying to update a few parts of the code and it is currently using `std::function`, ... I've updated the example to show approximately how its being used in the real code – vmp Apr 15 '22 at 04:58

2 Answers2

3

Member functions require a specific class object to invoke, so you need to do

function<void(S*, int, double)> fp = &S::f; 
fp(this, x, d);

Or use lambda to capture a specific class object and invoke its member function internally

function<void(int, double)> fp = [this](int x, double d) { this->f(x, d); };
fp(x, d);

Demo

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
1

Note: I'm presenting an alternative approach because the OP could not justify the use of std::function (and because an answer to the literal question has already been given by 康桓瑋).


As long as you are just choosing between member functions with the same signature to invoke, a std::function is has unnecessary overhead. The overhead is probably not critical, and you do have more flexibility if you need to expand beyond a simple member function call. On the other hand, a pointer-to-member-function provides similar functionality without the overhead.

The downside (for me) is the syntax. The type in this case is void (S::*)(int, double), which is hard to read if you're not used to pointers-to-member. On top of that, the variable being declared goes in the middle of the type, which again hurts readability. Still, if you can accept the syntax:

// Placeholder for the decision-making process:
char choose_fun() { return 'g'; }

// Same setup for `S` as in the question, except with the following added:
void S::other_method(int x, double d)
{
    void (S::* fp)(int, double);  // Manual type specification
    // decltype(&S::other_method) fp;  // Possibly easier to read

    switch (choose_fun()) {
        case 'f': fp = &S::f; break;
        case 'g': fp = &S::g; break;
        // ...
    }

    // More processing?

    // The syntax for invoking a pointer-to-member is also a bit weird.
    (this->*fp)(x, d);
}

Actually, if the setup really is as simple as presented in the question (a simple switch to decide what to call), I probably wouldn't bother with either pointers or std::function in this setup. I would store the value used by the switch, then call the functions via a helper function. (If there is no additional processing, then other_method simplifies to the helper function.)

// The helper function.
void S::choose_method(char choice, int x, double d)
{
    switch (choice) {
        // Returning void is legal and takes the place of having a `break`.
        case 'f': return f(x, d);
        case 'g': return g(x, d);
        // ...
    }
}

// Now the other method needs very little code in addition to the additional
// processing, whatever that is. This also brings the code closer to the
// ideal of one task per function.
void S::other_method(int x, double d)
{
    char choice = choose_fun();

    // More processing
        
    choose_method(choice, x, d);
}
JaMiT
  • 14,422
  • 4
  • 15
  • 31