10

我正在玩回调函数,并希望通过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包含std::function<void()>.

我在 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 {});
4

4 回答 4

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,而是使用 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)...); };
}
于 2019-08-29T11:57:03.440 回答
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)();
}

现场演示

根据cppreference

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

于 2019-08-29T13:02:04.370 回答
4

的特点之一std::bind是它对额外参数的作用。考虑:

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之前的工作。它可以用任何一组参数调用。它可以在没有参数的情况下调用(即它可以转换为std::function<void()>),并且可以通过 an 调用int(即它可以转换为std::function<void(int)>)。也就是说,变体的两种选择都可以从绑定表达式中构造出来,我们无法区分其中一种。他们都只是转换。因此,模棱两可。

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

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

于 2019-08-29T12:40:40.003 回答
-1

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

于 2019-08-29T11:59:43.593 回答