在同一个函数中有 2 个 Lambda 表达式相互调用

Have 2 Lambda expressions within the same function call each other

我一直在为一个项目使用 SFML,我试图在仍然使用 SFML 游戏循环的同时进入暂停状态。长话短说,我一直在尝试在同一个函数中使用 2 个 Lambda 表达式来在我有 运行ning 的暂停和 运行 状态之间切换。下面是一些示例代码:

int main() {
  //some stuff here...
  auto pauseloop = [&] {
    //do some stuff here...
    runloop();
  }
  auto runloop = [&] {
    //some more code, include SFML game loop here...
    if (paused)
      pauseloop();
  }
  runloop();
}

起初工作正常,但 运行 几乎立即陷入了一个问题:从 runloop() 中调用 pauseloop() 时,我无法从 runloop() 中调用=12=]。我试图在我的 main() 函数之上添加一些效果:

auto pauseloop = [&] {}; //initializer
auto runloop = [&] {}; //initializer

但是,经过一些广泛的测试和调试后,这会导致 main() 函数上方的 runloop() Lambda 表达式在从 pauseloop() 调用时被调用,但是 intended, fully declared, runloop()main() 函数结束时调用runloop()

首先,有没有办法让同一个函数中的两个Lambda表达式相互调用?第二,为什么 pauseloop() 表达式调用 runloop() 的初始化版本,而 main() 末尾的 runloop() 调用预期的版本?根据我的最佳猜测,我会假设因为 runloop()pauseloop() 都在调用任何一个之前被初始化定义,所以 pauseloop() 会调用声明的版本,因为它已被读入它的全部并正确地覆盖了初始化程序,但与实际发生的情况相比,这似乎完全不对劲。提前致谢!

编辑 1:简而言之,上述尝试不适用于预期的解决方案(让 Lambda 表达式“a”调用 Lambda 表达式“b”,反之亦然)。我将如何编写两个可以的 Lambda 表达式?

只要你的 lambda 有捕获,而且看起来像你的,你就不能让它们直接相互调用,因为当你声明它们时存在循环依赖。要解决此问题,您可以将它们分配给指针。

如果您没有捕获,编译器会简单地为您的 lambda 生成匿名函数,您可以像这样将它们分配给函数指针:

void (*pauseloop)() = nullptr;
void (*runloop)() = nullptr;

pauseloop = []() {
    //...
    runloop();
    //...
};

runloop = []() {
    //...
    pauseloop();
    //...
};

由于您确实有捕获,解决方案非常相似,但您会改用 std::function。实际上,由于您也可以将 std::function 与函数一起使用,因此无论如何这是我的建议:

std::function<void()> pauseloop, runloop;

pauseloop = [&]() {
    // ...
    runloop();
    // ...
};

runloop = [&]() {
    // ...
    pauseloop();
    // ...
};

请注意,在这两种情况下,仅通过值捕获函数指针/std::function 是不够的。至少有一个 lambda 表达式必须通过引用捕获它或将其用作全局变量。否则它只会捕获初始 nullptr 值。

https://ideone.com/c7FGCo

正如您所发现的,通过捕获简单地使用它们中的每一个都会产生循环依赖。另一种方法是将它们作为参数传递。

让我们从 runloop 开始。您可以将其作为参数传递,而不是通过捕获获取 pauseloop

auto runloop = [&](auto const& pauseloop) {
    if (pause) {
        pauseloop();
    }
};

以后你可以这样称呼它:runloop(pauseloop);

类似地,您可以给 pauseloop 一个参数:

auto pauseloop = [&](auto const& runloop) {
    runloop();
};

但是,现在在 pauseloop 中,您正在调用 runloop(),尽管 runloop 实际上需要一个参数。同样,在 runloop 中,尽管 pauseloop 也需要一个参数,但您正在调用 pauseloop()。要修复它,您还可以将它们中应该调用的参数传递给外部函数:

auto runloop = [&](auto const& runloop, auto const& pauseloop) {
    if (pause) {
        pauseloop(runloop, pauseloop);
    }
};

auto pauseloop = [&](auto const& runloop, auto const& pauseloop) {
    runloop(runloop, pauseloop);
};

现在,您可以这样调用 runlooppauseloop

runloop(runloop, pauseloop);
pauseloop(runloop, pauseloop);

但是,最终的调用界面非常冗长。为了更简单,您可以将接口和实现分开:

auto _runloop_impl = [&](auto const& runloop, auto const& pauseloop) {
    if (pause) {
        pauseloop(runloop, pauseloop);
    }
};

auto _pauseloop_impl = [&](auto const& runloop, auto const& pauseloop) {
    runloop(runloop, pauseloop);
};

auto runloop = [&] {
    _runloop_impl(_runloop_impl, _pauseloop_impl);
};

auto pauseloop = [&] {
    _pauseloop_impl(_runloop_impl, _pauseloop_impl);
};

现在,您可以简单地称呼它们:

runloop();
pauseloop();
  • 注意,直接调用lambda只需要定义接口

如果您有许多不同类型的循环要添加到逻辑中,您可以使用参数包轻松传递所有参数:

auto _runloop_impl = [&](auto const& ... loops) {
    auto loops_tuple = std::tuple{std::cref(loops)...};
    switch(state) {
        case State::PAUSE:
            std::get<1>(loops_tuple)(loops...);
            break;
        case State::MENU:
            std::get<2>(loops_tuple)(loops...);
            break;
        case State::SHOP:
            std::get<3>(loops_tuple)(loops...);
            break;
    }
};

auto _pauseloop_impl = [&](auto const& ... loops) {
    auto loops_tuple = std::tuple{std::cref(loops)...};
    std::get<0>(loops_tuple)(loops...);
};

////////////////////////////////////////////////////
//
// ... similar implementations for other loops
//
////////////////////////////////////////////////////


auto runloop = [&] {
    _runloop_impl(
        _runloop_impl,
        _pauseloop_impl,
        _menuloop_impl,
        _shoploop_impl
    )
};


int main() {
    runloop();
}