【问题标题】:std::bind to a std::variant containing multiple std::function typesstd::bind 到包含多个 std::function 类型的 std::variant
【发布时间】:2020-01-02 16:28:21
【问题描述】:

我正在玩回调函数,并希望通过std::bind 注册多个签名不同的函数(尽管它们都返回void)。将std::bind 的结果分配给std::variant 会导致“转换为非标量类型”错误。是歧义错误吗?我可以向编译器提供更多信息吗?

删除std::bind(允许分配)不是一个选项,因为我希望使用一些注册回调

template <typename Function, typename... Args>
void register(Function &&f, Args &&... args)
{
    variant_of_multiple_func_types = std::bind(f, args...);
}

例如:

std::variant<std::function<void()>, int> v = std::bind([]() noexcept {});

有效,但是

std::variant<std::function<void()>, std::function<void(int)>> v = std::bind([]() noexcept {});

没有,虽然我希望它编译成包含 std::function&lt;void()&gt;std::variant

我在 GCC 7.4.0 中使用-std=c++17 收到以下编译错误:

error: conversion from ‘std::_Bind_helper<false, main(int, char**)::<lambda()> >::type {aka std::_Bind<main(int, char**)::<lambda()>()>}’ to non-scalar type ‘std::variant<std::function<void()>, std::function<void(int)> >’ requested
     std::variant<std::function<void()>, std::function<void(int)>> v = std::bind([]() noexcept {});

【问题讨论】:

  • 半相关说明:为什么必须为工作示例中的 lambda 指定 noexcept 才能通过编译?
  • 您能否通过“为什么必须为工作示例中的 lambda 指定 noexcept”来解释您的意思?省略noexcept 仍然适用于我使用相同的编译器和标志。
  • 你为什么使用 std::bind 和 lambda? std::bind 在无法使用 lambda 时很有用。在您的代码中,std::bind 已经过时了。
  • std::bind([]() noexcept {})("lot", "of", "unused", "parameter") 有效。您可以将任意数量的参数传递给绑定对象,place_holder (std::bind([](int) {/*..*/}, place_holder::_5)) 最需要它。
  • @Jonas,哎呀,我错过了我启用了大多数警告。编译失败,因为指定了-Werror=noexcept

标签: c++ c++17 std-function std-variant


【解决方案1】:

std::bind 返回一个未指定的对象,该对象满足某些要求,但不允许基于签名区分函数类型。初始化

std::variant<std::function<void()>, std::function<void(int)>> v =
    std::bind([]() noexcept {});

只是模棱两可,和

一样
std::variant<int, int> v = 42; // Error, don't know which one

您可以明确说明您打算实例化的类型,例如

std::variant<std::function<void()>, std::function<void(int)>> v =
    std::function<void()>{std::bind([]() noexcept {})};

这需要一些类型别名,但基本上可以。更好的选择可能是避免使用std::bind,而是使用 lambdas。示例:

template <typename Function, typename... Args>
void registerFunc(Function &&f, Args &&... args)
{
    variant_of_multiple_func_types =
       [&](){ std::forward<Function>(f)(std::forward<Args>(args)...); };
}

【讨论】:

    【解决方案2】:

    原因是std::bind 导致与std::function 不同的类型(它是未指定/可调用类型)- 所以在可转换时- 它是模棱两可的。

    【讨论】:

      【解决方案3】:

      std::bind 的一个特点是它对 extra 参数的作用。考虑:

      int f(int i) { return i + 1; }
      auto bound_f = std::bind(f, 42);
      

      bound_f() 调用f(42) 得到43。但是也是bound_f("hello")bound_f(2.0, '3', std::vector{4, 5, 6}) 给你43 的情况。调用站点上没有关联占位符的所有参数都将被忽略。

      这里的意义在于is_invocable&lt;decltype(bound_f), Args...&gt;对于Args...的所有类型集合都为真


      回到你的例子:

      std::variant<std::function<void()>, std::function<void(int)>> v =
          std::bind([]() noexcept {});
      

      右边的绑定很像前面的bound_f。它可以用 any 组参数调用。它可以不带参数调用(即它可以转换为std::function&lt;void()&gt;),它可以用int 调用(即它可以转换为std::function&lt;void(int)&gt;)。也就是说,变体的两种选择都可以从绑定表达式中构造出来,我们无法区分其中一种。他们都只是转换。因此,模棱两可。

      我们不会对 lambdas 有这个问题:

      std::variant<std::function<void()>, std::function<void(int)>> v =
          []() noexcept {};
      

      这很好用,因为 lambda 只能在没有参数的情况下调用,所以只有一种选择是可行的。 Lambda 不只是丢弃未使用的参数。

      这概括为:

      template <typename Function, typename... Args>
      void register(Function &&f, Args &&... args)
      {
          variant_of_multiple_func_types =
              [f=std::forward<Function>(f), args=std::make_tuple(std::forward<Args>(args)...)]{
                  return std::apply(f, args);
              });
      }
      

      虽然如果您想在此处实际传递占位符,这是行不通的。这实际上取决于您更大的设计,这里可能是正确的解决方案。

      【讨论】:

        【解决方案4】:

        你可以使用 c++20 std::bind_front 它会编译:

        #include <functional>
        #include <variant>
        
        int main()
        {
            std::variant<std::function<void()>, std::function<void(int)>> v = std::bind_front([]() noexcept {});
            std::get<std::function<void()>>(v)();
        }
        

        Live demo

        根据cppreference

        此函数旨在替换std::bind。与std::bind 不同,它不支持任意参数重新排列,并且对嵌套的绑定表达式或std::reference_wrappers 没有特殊处理。另一方面,它关注调用包装对象的值类别,并传播底层调用操作符的异常规范。

        【讨论】:

        • Jason Turner 最近在 C++ Weekly174177181 集中讨论了他重新实现 bind_front 的尝试。代替 C++20 可用性可能会很有趣。
        • 在我们故意不接受任何额外参数的情况下使用bind_front 很奇怪。我们使用bind_front(f),而f 可以正常工作。
        猜你喜欢
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        • 2016-12-18
        • 2013-04-16
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        相关资源
        最近更新 更多