0

I am looking to make a class in C++ which can have a changeable function as one of its individual properties. Is there any standard way to achieve this? What would the syntax look like?

For example, something I have tried and want to do:

#include <iostream>

class Thing {
  private:
    
    int val;
    
    void func();
    
  public:
    
    Thing(int val = 0) {
      this->val = val;
    }
    
    void setFunc(void func()) { this->func() = func; }

    void useFunc() { this->func(); }
    
};

int main() {
  
  Thing thing1 = Thing(1);
  
  thing1.setFunc({
    
    std::cout << "i am thing 1, my value is: " << this->val;
    
  });
  
  thing1.useFunc();
  
  return 0;

}

I'm not sure what the syntax would be like for something like this, or if pointers are needed. This (probably obviously) returns a couple of errors, those being:

./main.cpp:16:46: error: expression is not assignable
    void setFunc(void func()) { this->func() = func; }
                                ~~~~~~~~~~~~ ^
./main.cpp:28:51: error: invalid use of 'this' outside of a non-static member function
    std::cout << "i am thing 1, my value is: " << this->val;
                                                  ^

I realize I could use a lot of properties that aren't functions instead, then have one method which uses these properties to determine the outcome, however I am not looking to do this, as it would require a lot more properties and I want to try something different for the sake of learning how to do it. In this example there'd be no point in doing what I am trying to do, but this is just an example.

MWRazer
  • 13
  • 3
  • 8
    Function pointer? `std::function`? – Alan Birtles Aug 28 '23 at 18:26
  • Are you looking for function ponters or lambda expressions? – πάντα ῥεῖ Aug 28 '23 at 18:27
  • Reading for later: [Pointers to Member Functions](https://isocpp.org/wiki/faq/pointers-to-members). You know you're going to want to do this sooner-or-later. – user4581301 Aug 28 '23 at 18:33
  • Another note: The lambda you are trying to define and pass to `setFunc` is not a member of `Thing` so `this->val` will not work. – Ted Lyngmo Aug 28 '23 at 19:16
  • IMHO, you should have multiple child classes. The parent class defines the virtual function declaration. Each child will have a different implementation of the virtual function. – Thomas Matthews Aug 28 '23 at 19:52
  • 1
    It's trivial to use either `std::function` or a generic function pointer for this, BUT your example is expecting that the function has access to private members which is not okay. You can't allow an interface that lets any arbitrary function or lambda access privates. – paddy Aug 28 '23 at 19:57

1 Answers1

1

Step 1: Define a function pointer!

class Thing {
  private:

    int val;
    // void func(); declares a function that will be defined later
    // not what we want.
    using func_ptr = void (*)(); // define a new type. It make everything 
                                 // that follows much easier 
    func_ptr func; // declares func to be a pointer to a function

  public:

    Thing(int val = 0): val(val) // use member initializer list
                                 // all members must be initialized
                                 // before entering the body of the
                                 // constructor. not that important
                                 // for an int, but very important for
                                 // types with expensive initialization
                                 // or no default initialization
    {
    }

    void setFunc(func_ptr func)  // pass a function pointer
    {
        this->func = func; // Do not call the function!
                           // avoid reusing names in the same scope.
                           // It only leads to trouble
    }

    void useFunc()
    {
        func(); // which func is not ambiguous here. No need for this
    }
};

Step 2: Assigning the function!

  thing1.setFunc({
    
    std::cout << "i am thing 1, my value is: " << this->val;
    
  });

Looks like you're trying to use a Lambda Expression. A couple of problems here.

  1. Wrong syntax. C++ is NOT a language that rewards guessing. Get a good book or two and learn the syntax in a controlled manner. The alternative is slow, painful, and not worth doing.

Proper syntax looks like

  thing1.setFunc([](){

      std::cout << "i am thing 1, my value is: " << this->val;

    });
  1. Unfortunately the Lambda has no clue that it is associated with thing1 nor is it a member of Thing so it cannot use this. This is a bit of a show-stopper. The Lambda need to Capture thing1, but as soon as you capture, you can't use function pointers anymore. They are too simple to deal with captures. Enter std::function.

Step 3: Start over with std::function

#include <iostream>
#include <functional>

class Thing {
  private:

    int val;
    using func_ptr = std::function<void ()>; 
    func_ptr func; 

  public:

    Thing(int val = 0): val(val) 
    {
    }

    void setFunc(func_ptr func)  
    {
        this->func = func; 
    }

    void useFunc()
    {
        func(); 
    }

    int get_val()
    {
        return val;
    }
};
int main() {

  Thing thing1 = Thing(1);
  thing1.setFunc([thing1](){ // captures by value. Not always what you 
                             // want, but generally the safest option.
                             // Just fine for a simple example like this
      std::cout << "i am thing 1, my value is: " << thing1.val;

    });

  thing1.useFunc();

  return 0;

}

And this still can't work because Thing::val is a private member and cannot be accessed from outside Thing or a friend of Thing. The Lambda is neither and cannot easily be shaped into one. Either A) the Lambda must call a Thing member function to do the real work, pointless because you'd discard the all of this rigamarole and simply directly call the member function, B) you add a getter for Thing::val and the Lambda uses the getter, or C) you make Thing::val public.

B looks like the best option. But what if the logic you need has to change thing1? That needs a...

Step 4: Find a simpler answer to the overarching problem.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • 1
    If the function should always execute in the context of the object that invokes it, then it should probably accept a `Thing&` parameter instead of having an object being captured in the lambda (by reference, or in your example by copy). – paddy Aug 28 '23 at 20:02
  • Great point. [Makes the tangent into `std::function` kind of pointless](https://godbolt.org/z/8raeaqYsd). – user4581301 Aug 28 '23 at 20:09
  • I wouldn't say it's necessarily pointless. Lambdas can still capture other values, making them more versatile than straight functions. Although it's hard to know from the original question whether the entire design is [XY Problem](https://meta.stackexchange.com/q/66377/200806). – paddy Aug 28 '23 at 20:30