std :: bind到包含多种std :: function类型的std :: variant

时间:2019-08-29 11:41:20

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

我正在使用回调函数,希望通过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::variant的{​​{1}}。

我在std::function<void()>的GCC 7.4.0中收到以下编译错误:

-std=c++17

4 个答案:

答案 0 :(得分:9)

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,而是也使用lambda。示例:

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

答案 1 :(得分:7)

您可以使用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不同,它不支持任意参数重排,并且对嵌套的bind-expressions或std::reference_wrapper没有特殊处理。另一方面,它关注调用包装对象的值类别,并传播基础调用操作程序的异常规范。

答案 2 :(得分:4)

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<decltype(bound_f), Args...>对所有类型的Args...都是正确的


回到您的示例:

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

右侧的绑定与bound_f之前的工作原理非常相似。可以使用 any 一组参数进行调用。它可以不带任何参数调用(即,可以转换为std::function<void()>),可以使用int调用(即可以转换为std::function<void(int)>)。也就是说,可以从bind表达式构造变体的两个替代方案,并且我们无法将一个与另一个区分开。他们都是转化。因此,模棱两可。

我们不会 遇到lambda的问题:

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);
        });
}

尽管如果您想在此处实际传递占位符,则无法使用。真正取决于您的较大设计,这里可能是正确的解决方案。

答案 3 :(得分:-1)

原因是std::bindstd::function的结果类型不同(它是未指定/可调用的类型),因此可转换时它是模棱两可的。