Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor parameter access member field of object under construction

Tags:

c++

I have the following structure that has a member function pointer. My question is, is there a way for the lambda that I pass to it to refer to its own member variable? i.e.

struct Foo
{
   int aVar{1};
   int (*funcPtr)(int);
   
   Foo(int (*func)(int)) : funcPtr(func) {};
};

auto bar1 = Foo([](int a) {std::cout << a;}) // ok
auto bar2 = Foo([](int a) {std::cout << a + bar2.aVar;}) // Not ok but is there a way to access the member variable of the object currently being defined?

Can I achieve something to this effect?

What I would like to achieve here is a process to automatically generate objects based on the lambda you pass in. e.g: bar2 above is an object that can return anything plus its stored value. i.e. I would like bar2.funcPtr(5) == 5 + bar2.aVar to be an invariant of the class. In the future I might need another object that can return anything minus its stored value, and I only need to pass the corresponding lambda to do that (if the lambda can access the member fields), without defining a new class or method.

like image 918
RelativisticPenguin Avatar asked Oct 20 '25 03:10

RelativisticPenguin


2 Answers

The lambda must have the signature int(int) but your lambda has the signature void(int) so that's the first problem.

The other is that the lambda must capture bar2. You could use std::function for that.

#include <iostream>
#include <functional>

struct Foo {
   int aVar{1};

   std::function<int(int)> funcPtr;
   
   Foo(std::function<int(int)> func) : funcPtr(func) {};

   int call(int x) { return funcPtr(x); }
};

int main() {
    Foo bar2([&](int a) { return a + bar2.aVar; });

    std::cout << bar2.call(2);   // prints 3
}

A more practical solution would be to not tie the lambda to the instance for which it was originally created but to let it take a Foo& as an argument (and not to capture it). Moving it around doesn't become such a hassle with this approach.

Example:

struct Foo {
   int aVar;

   int(*funcPtr)(Foo&, int); // takes a Foo& as an argument
   
   Foo(int x, int(*func)(Foo&, int)) : aVar(x), funcPtr(func) {};

   int call(int x) { return funcPtr(*this, x); }
};

int main() {
    Foo bar2(10, [](Foo& f, int a) { return a + f.aVar; });

    std::cout << (bar2.call(5) == bar2.aVar + 5) << '\n';    // true

    Foo bar3(20, [](Foo& f, int a) { return a * f.aVar; });

    std::cout << bar2.call(2) << '\n'; // 10 + 2 = 12
    std::cout << bar3.call(2) << '\n'; // 20 * 2 = 40

    std::swap(bar2, bar3);

    std::cout << bar2.call(2) << '\n'; // swapped, now 40
    std::cout << bar3.call(2) << '\n'; // swapped, now 12
}
like image 105
Ted Lyngmo Avatar answered Oct 21 '25 17:10

Ted Lyngmo


Foo( /* ... */ )

As used in the shown code this is a constructor call. This is constructing a new object, right there.

Before the new object can be created and its constructor get invoked, the parameters to the constructor must be evaluated. This is how C++ works. There are no alternatives, or workarounds, that end up constructing the object first and only then evaluate its constructor's parameters afterwards.

For this reason it is logically impossible for a lambda, that gets passed as parameter to the constructor, "refer to its own member variable". There is nothing in existence that has "its own member variable" at this point. The object's construction has not began, and you cannot refer to an object or a member of an object that does not exist. The object cannot be constructed until the constructor's parameters get evaluated, first. C++ does not work this way.

You will need to come up with some alternative mechanism for your class. At which point you will discover another fatal design flaw that dooms the shown approach:

int (*funcPtr)(int);

This is a plain function pointer. In order for lambda to reference an object that it's related to, in some form or fashion, it must capture the object (by reference, most likely). However lambdas that capture (by value or reference), cannot be converted to a plain function pointer. Only capture-less lambdas can be converted to a plain pointer.

At the bare minimum this must be a std::function, instead.

And now that it's a std::function, you can capture its object, by reference, in the lambda, and assign it to the std::function.

But this is not all, there is another problem that you must deal with: in order for all of this to work it is no longer possible for the object to be moved or copied in any way. This is because the lambda captured a reference to the original object that was constructed, full stop.

And the fact that the constructed object gets copied or moved does not, in some form or fashion, modify the lambda so it now magically captures the reference to the copy or the moved instance of the original object.

None of these are insurmountable problems, but they will require quite a bit of work to address, in order to have a well-formed C++ program as a result.

like image 45
Sam Varshavchik Avatar answered Oct 21 '25 17:10

Sam Varshavchik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!