1

Let's say I have some function:

void myFunction(void (MyClass::*function)(int), MyClass& myClass) {
    (myClass.*function)(1);
}

Which is called as follows:

class MyClass {
    int value;

    void setValue(int value) {
        this->value = value;
    }
}

MyClass myClass;
myFunction(&MyClass::set_value, myClass);

This function takes the object myClass and says to call its member function setValue, which it calls with the parameter set to 1.

Before I realized C++ had support for passing member functions as as parameters, I had manually set my code to take in a object and function pointer and then determine the offset between their addresses. This offset could then be saved to call the same member function if given a different instance of the same type of object.

But yeah, then I discovered you can just do Class::*function which (I assume) does exactly what I had previously built/described.

Which leads me to my issue, let's say I have the same function as before, except I change my class structure a bit:

class MyClass {
    ChildClass childClass;
}

class ChildClass {
    int value;

    void setValue(int value) {
        this->value = value;
    }
}

Is there then a way in C++ to pass the setValue function into myFunction given that setValue is now within the ChildClass object within MyClass?

The most obvious solution would be to add a setValue member function to MyClass which calls the setValue function within its childClass object, like so:

class MyClass {
    ChildClass childClass;

    void setValue(int value) {
        this->childClass.setValue(value);
    }
}

class ChildClass {
    int value;

    void setValue(int value) {
        this->value = value;
    }
}

MyClass myClass;
myFunction(&MyClass::setValue, myClass);

However, due to the semantics of the project I am working on this would not be a desirable solution. Thus, I was curious about whether or not there is a way to achieve this in C++. If not, I can certainly just implement it manually (it would be the same as before, except you would save the offset between the childClass member and MyClass as well as the offset of the setValue member and ChildClass), but naturally I'd like to know if there is a more "built-in" way of achieving this.

Shane Duffy
  • 1,117
  • 8
  • 18
  • I'm fairly certain that everything you have here is in C++98. – Kaz Nov 13 '19 at 23:37
  • Thanks, I wasn't sure. Edited out C++11 comment. – Shane Duffy Nov 13 '19 at 23:41
  • > *However, due to the semantics of the project I am working on this would not be a desirable solution.* It's hands-down the best solution. `MyClass` should act as a delegate for the contained `ChildClass` object; then all the existing code which expects `MyClass` to have a `setValue` continues to work unmodified. – Kaz Nov 14 '19 at 00:05

3 Answers3

2

Is there then a way in C++ to pass the setValue function into myFunction given that setValue is now within the ChildClass object within MyClass?

There is a way by adding a level of indirection. That level of indirection is polymorphic function wrapper std::function. myFunction can take a std::function argument, which can be initialized with arbitrary accessor functions, including labdas. E.g.:

#include <functional>

struct ChildClass {
    int value;

    void setValue(int value) {
        this->value = value;
    }
};

struct MyClass {
    ChildClass childClass;
};

void myFunction(std::function<void(MyClass&, int)> const& f, MyClass& myClass) {
    f(myClass, 1);
}

int main() {
    MyClass myClass;
    myFunction([](MyClass& a, int b) { a.childClass.setValue(b); }, myClass);
}

Using std::function your can get rid of MyClass& argument and expect the user to provide the required object inside std::function captured by the lambda expression:

void myFunction2(std::function<void(int)> const& f) {
    f(1);
}

int main() {
    MyClass myClass;
    myFunction2([&myClass](int b) { myClass.childClass.setValue(b); });
}

Note that std::function can store at least sizeof(void(Undefined_class::*member_pointer)()) inside the object without allocating heap memory. The C++ standard doesn't require that, however, it seems that the reason for this optimization is that using std::function for member function pointers is not much worse than using member function pointers directly. Or, in other words, if you replace member and member function pointers in your code with std::function, your application won't be penalised by extra memory allocations for that. With gcc on x86_64 that results in 16 bytes of inline storage in std::function, which can store a lambda capture object with 2 object references.

The size of std::function is still larger than that, though, and it is a non-trivial type, so it cannot be passed or returned in registers.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • This stuff breaks encapsulation; the original solution of allowing `MyClass` to be a delegate for `ChildClass` is far superior. Now you have a `main` function which knows that the `myClass` object has a child called `childClass`. Code which used to call `myFunction(&MyClass::setValue, myClass);` cannot "just work" with the change to the child object; all these calls have to be edited to use lambdas. – Kaz Nov 13 '19 at 23:56
  • 1
    @Kaz To maintain encapsulation just add a member function to `MyClass`: `auto getSetter() { return [this](int b) { childClass.setValue(b); }; }`. You can encapsulate even deeper by returning `std::function` - this works with Pimpl idiom. – Maxim Egorushkin Nov 14 '19 at 00:00
  • ^ But that is now similar to, but ironically complicated compared to just `MyClass::setValue(int value) { this->childClass.setValue(value); }`. We didn't avoid injecting a function into `MyClass` as OP wanted, and we made it worse by turning it into a convoluted function-returning function. Why would we want to create a new function object, if we already have an OOP object with methods. – Kaz Dec 10 '19 at 17:34
2

There is a solution with c++17 fold expressions:

#include <iostream>

struct S
{
    int foo() {return 1;} 
};

struct T
{
    S s;
};


template<class ... Args>
int call(T &t, Args... args)
{
    return (t .* ... .* args)();
}

int main()
{
    T t{};
    std::cout << call(t, &T::s, &S::foo);
}

You have to explicitly give the whole "path" to the member function, but I think this is unavoidable (just from a logical point of view). This scales to an arbitrary level of indirection. You can also template also the T and it shouldn't be to bad to add functions arguments.

n314159
  • 4,990
  • 1
  • 5
  • 20
  • That's pretty cool. However, that requires `call` to be a function template, which may or may not be feasible. – Maxim Egorushkin Nov 13 '19 at 23:26
  • This is a cool feature, I didn't realize this was even possible. However Maxim's answer appears to fit into the other conditions of my project quite well. Regardless, this is an interesting solution. – Shane Duffy Nov 13 '19 at 23:33
  • 1
    Fold expression on pointer-to-member access operator. Now I've seen it all – M.M Nov 14 '19 at 00:13
  • I think I saw a yt-video a week ago (maybe an older C++-Weekly?) where the host said, that he couldn't imagine any possible usecase for that.^^ – n314159 Nov 14 '19 at 11:16
  • ah, found [it](https://youtu.be/AqDsso3S5fg?t=819). Great talk by @lefticus – n314159 Nov 14 '19 at 18:25
0

Does this do what you want?

class ChildClass {
public:
    int value;

    void setValue(int value) {
        this->value = value;
    }
} ;

class MyClass {
public:
    ChildClass childClass;
} ;

static void myFunction(void (ChildClass::*f)(int), MyClass& myClass) {
  (myClass.childClass.*f) (1) ;
}

int main() {
  MyClass myClass;
  myFunction(&ChildClass::setValue, myClass);
}

Note the semi-colons after the class definitions.

TonyK
  • 16,761
  • 4
  • 37
  • 72
  • Not exactly, myFunction must reference only MyClass without any reference to ChildClass. Rather, I want to know if there is a standard way to specify this information entirely within the call to myFunction. It would certainly be possible to achieve this by manually looking at address offsets, however it would be convenient if there was a standard way of doing this. – Shane Duffy Nov 13 '19 at 22:45
  • Also, the reason for this is that the project I am working on has an unknown template in place of MyClass. It should be able to take in any member function of the template but also any member function any number of layers deep within the template object's children. – Shane Duffy Nov 13 '19 at 22:50