I have a compile-time error if I pass a lambda while capturing a variable.
Solutions out there are bound and tied to a specific class but essentially it's a use case with 1 to many class types (Observees). It's a kind of polymorphism.
Edit Richard in the comments pointed out the reason but I wonder if there's a workaround to achieve this, I ran into a blocker with free function pointers (see linked question), any lateral thinking?
Observee1
class Test {
int _value;
public:
void testMethod(int value) {
_value = value 1;
}
};
Observee2 - A different class but same signature
class AnotherTest {
int _value;
public:
void anotherMethod(int value) { // same parameters, different implementation
_value = value * 2;
}
};
Observer
class Observer {
protected:
void (*_notify)(int value); // method called at runtime
public:
explicit Observer(void (*notify)(int value));
};
Attempted solution
auto *test = new Test();
auto *anotherTest = new AnotherTest();
auto *observer = new Observer([](int value) { // works
});
auto *observer2 = new Observer([test](int value) { // capturing test throws an error.
// requirement is to use captured object here.
test->testMethod(4);
});
auto *anotherObserver = new Observer([anotherTest](int value) {
anotherTest->testMethod(5);
});
CodePudding user response:
The first comment fundamentally answers your question: A capturing lambda is not convertible to a function pointer unless the capture list is empty.
By using a std::function<void(int)>
instead of a function pointer, this limitation is resolved.
#include <functional>
class Test {};
class AnotherTest {};
class Observer {
protected:
std::function<void(int)> _notify;
public:
explicit Observer(std::function<void(int)> notify): _notify(notify) {}
};
int main() {
auto *test = new Test();
auto *anotherTest = new AnotherTest();
auto *observer = new Observer([](int value) {});
auto *observer2 = new Observer([test](int value) {});
auto *obserer3 = new Observer([anotherTest](int value) {});
}
CodePudding user response:
If you have to use old plain C function pointer and can't migrate to std::function<void(int)>
(as suggested in other answer), then I implemented following quite hacky but working solution.
To skip explanations just put a look at usage examples, Test1()
function solves exactly your task with your classes, Test0()
provides more advanced my own examples.
I created two helper functions - FuncPtr<FuncT>(lambda)
accepts single lambda and resulting C function pointer type, this function converts any lambda (even with captures) to plain C style function pointer. This function is designed to be used without loops, if call to this function passes two times then it will return same pointer value even if lambda capture variables are different. Use this function only if this function is called just once, for example at the beginning of main().
Second function FuncPtrM<FuncT>(idx, lambda)
is designed to be repeatable. You can call it for same lambda many times. For example in loops or if you call it many times inside other function body. Besides lambda you have to pass runtime index idx
which will save this lambda into slot idx
. Number of slots is limited but quite large, tens thousands. If you call this function with same lambda and same index then old lambda value will be overwritten into same index.
See Test0()
which uses both FuncPtr()
and FuncPtrM()
, first one is used single time, second is used many time in a loop. Loop example, as you can see in console output (after code), gives each equal value twice, this happens because same slot number is reused twice, hence previous value of lambda in a slot is overwritten.
Updated: Just created third helper function FuncPtrC()
that should be probably used always instead of first two functions if you don't want to be confused with first two functions. This function keeps a counter updated by itself and doesn't need any thinking.
#include <vector>
#include <functional>
#include <iostream>
#include <memory>
#include <iomanip>
template <typename Tag, typename InnerFunc>
class Caller {
public:
template <typename ResT, typename ... Args>
static ResT Call(Args ... args) {
return (*inner_)(std::forward<Args>(args)...);
}
template <typename ResT, typename ... Args>
static auto Ptr(ResT (*ptr)(Args...)) {
return &Call<ResT, Args...>;
}
template <typename ... Args>
static void Set(Args && ... args) {
inner_ = std::make_shared<InnerFunc>(
std::forward<Args>(args)...);
}
private:
static inline std::shared_ptr<InnerFunc> inner_;
};
template <typename FuncT, typename Tag = FuncT, typename T>
FuncT * FuncPtr(T && f) {
static Caller<Tag, T> caller;
caller.Set(std::forward<T>(f));
return caller.Ptr((FuncT*)nullptr);
}
template <typename FuncT, size_t I = 0, size_t Depth = 2, typename T>
FuncT * FuncPtrM(size_t idx, T && func) {
if constexpr(Depth == 0)
if (idx >= 16)
throw std::runtime_error(
"Provided table runtime index too large!");
switch (idx % 16) {
#define C(i) case i: { \
if constexpr(Depth == 0) \
return FuncPtr<FuncT, std::integral_constant< \
size_t, I>>(std::forward<T>(func)); \
else \
return FuncPtrM<FuncT, I * 16 i, \
Depth - 1>(idx / 16, std::forward<T>(func)); \
break; \
}
C( 0) C( 1) C( 2) C( 3) C( 4) C( 5) C( 6) C( 7) C( 8) C( 9)
C(10) C(11) C(12) C(13) C(14) C(15)
default:
throw std::runtime_error(
"This shouldn't happen! Programming logic error.");
}
}
template <typename FuncT, typename T>
FuncT * FuncPtrC(T && func) {
static size_t counter = 0;
return FuncPtrM<FuncT>(counter , std::forward<T>(func));
}
void Test0() {
using F = int (int value);
int i = 7;
{
F * ptr = FuncPtr<F>([i](auto v){ return v i; });
std::cout << ptr(10) << std::endl;
}
std::cout << std::endl;
{
std::vector<F*> funcs;
for (size_t j = 0; j < 50; j)
for (size_t k = 0; k < 2; k)
funcs.push_back(FuncPtrM<F>(j,
[j, k](auto v){ return v j * 2 k; }));
for (size_t i = 0; i < funcs.size(); i) {
std::cout << std::setfill(' ') << std::setw(3)
<< funcs[i](10) << " ";
if ((i 1) % 15 == 0)
std::cout << std::endl;
}
}
std::cout << std::endl;
{
std::vector<F*> funcs;
for (size_t j = 0; j < 50; j)
for (size_t k = 0; k < 2; k)
funcs.push_back(FuncPtrC<F>(
[j, k](auto v){ return v j * 2 k; }));
for (size_t i = 0; i < funcs.size(); i) {
std::cout << std::setfill(' ') << std::setw(3)
<< funcs[i](10) << " ";
if ((i 1) % 15 == 0)
std::cout << std::endl;
}
}
}
class Test {
int _value;
public:
void testMethod(int value) {
_value = value 1;
}
};
class AnotherTest {
int _value;
public:
void anotherMethod(int value) { // same parameters, different implementation
_value = value * 2;
}
};
class Observer {
public:
using NotifyFunc = void (int value);
protected:
void (*_notify)(int value); // method called at runtime
public:
explicit Observer(void (*notify)(int value)) {
_notify = notify;
}
};
void Test1() {
using NotifyFunc = Observer::NotifyFunc;
auto *test = new Test();
auto *anotherTest = new AnotherTest();
auto *observer = new Observer(FuncPtr<NotifyFunc>(
[](int value) { // works
}));
auto *observer2 = new Observer(FuncPtr<NotifyFunc>(
[test](int value) { // capturing test throws an error.
// requirement is to use captured object here.
test->testMethod(4);
}));
auto *anotherObserver = new Observer(FuncPtr<NotifyFunc>(
[anotherTest](int value) {
anotherTest->anotherMethod(5);
}));
}
int main() {
Test0();
Test1();
}
Output:
17
11 11 13 13 15 15 17 17 19 19 21 21 23 23 25
25 27 27 29 29 31 31 33 33 35 35 37 37 39 39
41 41 43 43 45 45 47 47 49 49 51 51 53 53 55
55 57 57 59 59 61 61 63 63 65 65 67 67 69 69
71 71 73 73 75 75 77 77 79 79 81 81 83 83 85
85 87 87 89 89 91 91 93 93 95 95 97 97 99 99
101 101 103 103 105 105 107 107 109 109
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
100 101 102 103 104 105 106 107 108 109