我正在使用回调函数,希望通过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
答案 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)();
}
根据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::bind
与std::function
的结果类型不同(它是未指定/可调用的类型),因此可转换时它是模棱两可的。