在捕获变量时将 lambda 传递给构造函数
passing a lambda to a constructor while capturing variables
如果我在捕获变量时传递 lambda,我会遇到编译时错误。
现有的解决方案绑定并绑定到特定的 class,但本质上它是一个具有 1 到许多 class 类型(观察者)的用例。这是一种多态性。
Edit Richard 在评论中指出了原因,但我想知道是否有解决方法来实现这一点,我 运行 进入了一个带有自由函数指针的拦截器(参见相关问题),有横向思维吗?
Observee1
class Test {
int _value;
public:
void testMethod(int value) {
_value = value + 1;
}
};
Observee2 - 不同的 class 但相同的签名
class AnotherTest {
int _value;
public:
void anotherMethod(int value) { // same parameters, different implementation
_value = value * 2;
}
};
观察者
class Observer {
protected:
void (*_notify)(int value); // method called at runtime
public:
explicit Observer(void (*notify)(int value));
};
尝试的解决方案
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);
});
错误
第一条评论从根本上回答了你的问题:
A capturing lambda is not convertible to a function pointer unless the capture list is empty.
通过使用 std::function<void(int)>
而不是函数指针,解决了这个限制。
#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) {});
}
如果您必须使用旧的普通 C 函数指针并且不能迁移到 std::function<void(int)>
(如其他答案中所建议的),那么我实施了以下非常 hacky 但可行的解决方案。
要跳过解释,只需看一下用法示例,Test1()
函数完全可以用您的 类 解决您的任务,Test0()
提供了我自己的更高级示例。
我创建了两个辅助函数 - FuncPtr<FuncT>(lambda)
接受单个 lambda 和生成的 C 函数指针类型,此函数将任何 lambda(即使有捕获)转换为纯 C 样式函数指针。此函数设计为无需循环使用,如果调用此函数两次,则即使 lambda 捕获变量不同,它也会 return 相同的指针值。仅当此函数仅被调用一次时才使用此函数,例如在 main() 的开头。
第二个函数FuncPtrM<FuncT>(idx, lambda)
被设计成可重复的。您可以多次调用相同的 lambda。例如在循环中,或者如果您在其他函数体内多次调用它。除了 lambda,您还必须传递运行时索引 idx
,这会将此 lambda 保存到插槽 idx
中。插槽数量有限但数量很大,数以万计。如果您使用相同的 lambda 和相同的索引调用此函数,那么旧的 lambda 值将被覆盖到相同的索引中。
参见Test0()
,它同时使用了FuncPtr()
和FuncPtrM()
,第一个被单次使用,第二个被多次循环使用。循环示例,正如您在控制台输出中看到的(在代码之后),给每个相等的值两次,这是因为相同的槽号被重复使用了两次,因此槽中 lambda 的先前值被覆盖。
已更新:刚刚创建了第三个辅助函数 FuncPtrC()
如果您不想与前两个函数混淆,则应该始终使用它而不是前两个函数职能。这个函数让一个计数器自己更新,不需要任何思考。有关 FuncPtrC()
.
用法的示例,请参阅 Test0()
#include <vector>
#include <functional>
#include <iostream>
#include <memory>
#include <iomanip>
#include <atomic>
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(bool allow_reuse, Args && ... args) {
if (!allow_reuse && inner_)
throw std::runtime_error("Same func ptr slot "
"initialized two times with same lambda!");
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, bool allow_reuse = false) {
static Caller<Tag, T> caller;
caller.Set(allow_reuse, 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, bool allow_reuse = false) {
if constexpr(Depth == 0)
if (idx >= 16)
throw std::runtime_error(
"Provided func ptr 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), allow_reuse); \
else \
return FuncPtrM<FuncT, I * 16 + i, \
Depth - 1>(idx / 16, std::forward<T>(func), allow_reuse); \
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 std::atomic<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; }, true));
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();
}
输出:
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
如果我在捕获变量时传递 lambda,我会遇到编译时错误。
现有的解决方案绑定并绑定到特定的 class,但本质上它是一个具有 1 到许多 class 类型(观察者)的用例。这是一种多态性。
Edit Richard 在评论中指出了原因,但我想知道是否有解决方法来实现这一点,我 运行 进入了一个带有自由函数指针的拦截器(参见相关问题),有横向思维吗?
Observee1
class Test {
int _value;
public:
void testMethod(int value) {
_value = value + 1;
}
};
Observee2 - 不同的 class 但相同的签名
class AnotherTest {
int _value;
public:
void anotherMethod(int value) { // same parameters, different implementation
_value = value * 2;
}
};
观察者
class Observer {
protected:
void (*_notify)(int value); // method called at runtime
public:
explicit Observer(void (*notify)(int value));
};
尝试的解决方案
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);
});
错误
第一条评论从根本上回答了你的问题: A capturing lambda is not convertible to a function pointer unless the capture list is empty.
通过使用 std::function<void(int)>
而不是函数指针,解决了这个限制。
#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) {});
}
如果您必须使用旧的普通 C 函数指针并且不能迁移到 std::function<void(int)>
(如其他答案中所建议的),那么我实施了以下非常 hacky 但可行的解决方案。
要跳过解释,只需看一下用法示例,Test1()
函数完全可以用您的 类 解决您的任务,Test0()
提供了我自己的更高级示例。
我创建了两个辅助函数 - FuncPtr<FuncT>(lambda)
接受单个 lambda 和生成的 C 函数指针类型,此函数将任何 lambda(即使有捕获)转换为纯 C 样式函数指针。此函数设计为无需循环使用,如果调用此函数两次,则即使 lambda 捕获变量不同,它也会 return 相同的指针值。仅当此函数仅被调用一次时才使用此函数,例如在 main() 的开头。
第二个函数FuncPtrM<FuncT>(idx, lambda)
被设计成可重复的。您可以多次调用相同的 lambda。例如在循环中,或者如果您在其他函数体内多次调用它。除了 lambda,您还必须传递运行时索引 idx
,这会将此 lambda 保存到插槽 idx
中。插槽数量有限但数量很大,数以万计。如果您使用相同的 lambda 和相同的索引调用此函数,那么旧的 lambda 值将被覆盖到相同的索引中。
参见Test0()
,它同时使用了FuncPtr()
和FuncPtrM()
,第一个被单次使用,第二个被多次循环使用。循环示例,正如您在控制台输出中看到的(在代码之后),给每个相等的值两次,这是因为相同的槽号被重复使用了两次,因此槽中 lambda 的先前值被覆盖。
已更新:刚刚创建了第三个辅助函数 FuncPtrC()
如果您不想与前两个函数混淆,则应该始终使用它而不是前两个函数职能。这个函数让一个计数器自己更新,不需要任何思考。有关 FuncPtrC()
.
Test0()
#include <vector>
#include <functional>
#include <iostream>
#include <memory>
#include <iomanip>
#include <atomic>
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(bool allow_reuse, Args && ... args) {
if (!allow_reuse && inner_)
throw std::runtime_error("Same func ptr slot "
"initialized two times with same lambda!");
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, bool allow_reuse = false) {
static Caller<Tag, T> caller;
caller.Set(allow_reuse, 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, bool allow_reuse = false) {
if constexpr(Depth == 0)
if (idx >= 16)
throw std::runtime_error(
"Provided func ptr 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), allow_reuse); \
else \
return FuncPtrM<FuncT, I * 16 + i, \
Depth - 1>(idx / 16, std::forward<T>(func), allow_reuse); \
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 std::atomic<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; }, true));
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();
}
输出:
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