在下面的示例中,我想要一个traverse
方法来接收回调。一旦我没有捕获任何[]
,此示例就可以完美运行,因为可以将lambda简化为函数指针。但是,在这种情况下,我想访问sum
。
struct Collection {
int array[10];
void traverse(void (*cb)(int &n)) {
for(int &i : array)
cb(i);
}
int sum() {
int sum = 0;
traverse([&](int &i) {
sum += i;
});
}
}
解决此问题的正确方法是什么(不使用任何模板)?一种解决方案是按如下方式使用typename
模板。但是在这种情况下,您无法了解每次迭代(int
)中遍历的含义:
template <typename F>
void traverse(F cb) {
for(int &i : array)
cb(i);
}
答案 0 :(得分:2)
Lambda类型未指定;无法命名它们。
因此,您有两种选择:
将traverse
用作模板(或使用auto
,实际上是同一件事)
幸运的是,这是一件完全正常且平常的事情。
让traverse
取std::function<void(int)>
。这会产生一些开销,但至少意味着该功能不必是模板。
但是在这种情况下,您无法了解每次迭代的遍历结果(整数)
我们不倾向于考虑这个问题。我确实知道,在函数的类型中提供此名称更为令人满意和明确,但是通常注释就足够了,因为如果回调不提供int
,您将获得一个编译错误。
答案 1 :(得分:2)
只有不可捕获的lambda可以与函数指针一起使用。由于每个lambda定义都有自己的类型,因此您必须在所有接受捕获的lambda的地方都使用模板参数。
但是在这种情况下,您无法了解每次迭代的遍历结果(整数)。
使用SFINAE可以轻松地检查此问题,或者使用C ++ 20中的概念甚至可以更简单地检查此问题。为了使它更简单,您甚至不需要定义一个概念并在以后使用它,您可以直接使用即席要求(这会导致requires
关键字的双重使用:< / p>
struct Collection {
int array[10];
template <typename F>
// check if F is "something" which can be called with an `int&` and returns void.
requires requires ( F f, int& i) { {f(i)} -> std::same_as<void>; }
void traverse(F cb)
{
for(int &i : array)
cb(i);
}
// alternatively you can use `std::invocable` from <concepts>
// check if F is "something" which can be called with an `int&`, no return type check
template <std::invocable<int&> F>
void traverse2(F cb)
{
for(int &i : array)
cb(i);
}
int sum() {
int sum = 0;
traverse([&](int &i) {
sum += i;
});
return sum;
}
};
答案 2 :(得分:0)
在您的情况下,您可以通过几种方法在C ++中声明回调:
void traverse(void (*cb)(int &n)) {
for(int &i : array)
cb(i);
}
此解决方案仅支持可以衰减为函数指针的类型。如您所述,具有捕获功能的Lambda不会成功。
template <typename F>
void traverse(F cb) {
for(int &i : array)
cb(i);
}
它确实接受任何东西,但是正如您所注意到的。该代码很难阅读。
void traverse(std::function<const void(int &num)>cb) {
for(int &i : array)
cb(i);
}
这是功能最全的解决方案,开销成本略高。
别忘了包含<functional>
。