Search code examples
c++classmethodsproperties

Having a function as a property on a class - C++


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.


Solution

  • 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.