std::bind 到包含多个 std::function 类型的 std::variant
std::bind to a std::variant containing multiple std::function types
我正在研究回调函数,希望通过 std::bind
注册多个签名不同的函数(尽管它们都是 return void
)。将 std::bind
的结果分配给 std::variant
会产生 "conversion to non-scalar type" 错误。这是歧义错误吗?我可以向编译器提供更多信息吗?
删除 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<void()>
.
的 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 {});
std::bind
returns 满足特定要求但不允许根据签名区分函数类型的未指定对象。初始化
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)...); };
}
原因是 std::bind
与 std::function
产生不同的类型(它是一个未指定/可调用类型)- 所以虽然可转换 - 但它是不明确的。
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
很像。它可以用 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);
});
}
虽然如果你真的想在这里传递占位符,这是行不通的。这真的取决于你更大的设计,这里的正确解决方案可能是什么。
您可以使用 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:
This function is intended to replace std::bind
. Unlike std::bind
, it does not support arbitrary argument rearrangement and has no special treatment for nested bind-expressions or std::reference_wrapper
s. On the other hand, it pays attention to the value category of the call wrapper object and propagates exception specification of the underlying call operator.
我正在研究回调函数,希望通过 std::bind
注册多个签名不同的函数(尽管它们都是 return void
)。将 std::bind
的结果分配给 std::variant
会产生 "conversion to non-scalar type" 错误。这是歧义错误吗?我可以向编译器提供更多信息吗?
删除 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<void()>
.
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 {});
std::bind
returns 满足特定要求但不允许根据签名区分函数类型的未指定对象。初始化
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)...); };
}
原因是 std::bind
与 std::function
产生不同的类型(它是一个未指定/可调用类型)- 所以虽然可转换 - 但它是不明确的。
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
很像。它可以用 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);
});
}
虽然如果你真的想在这里传递占位符,这是行不通的。这真的取决于你更大的设计,这里的正确解决方案可能是什么。
您可以使用 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:
This function is intended to replace
std::bind
. Unlikestd::bind
, it does not support arbitrary argument rearrangement and has no special treatment for nested bind-expressions orstd::reference_wrapper
s. On the other hand, it pays attention to the value category of the call wrapper object and propagates exception specification of the underlying call operator.