std::bind to a std::variant containing multiple std::function types
std::bind
returns an unspecified object that satisfies certain requirements, but doesn't allow for a distinction between function types based on a signature. The initialization
std::variant<std::function<void()>, std::function<void(int)>> v =
std::bind([]() noexcept {});
is simply ambiguous, same as
std::variant<int, int> v = 42; // Error, don't know which one
You can be explicit about the type you intend to instantiate, e.g.
std::variant<std::function<void()>, std::function<void(int)>> v =
std::function<void()>{std::bind([]() noexcept {})};
This cries for some type aliases, but basically works. A better alternative might be to avoid std::bind
and instead use lambdas, too. Example:
template <typename Function, typename... Args>
void registerFunc(Function &&f, Args &&... args)
{
variant_of_multiple_func_types =
[&](){ std::forward<Function>(f)(std::forward<Args>(args)...); };
}
You can using c++20 std::bind_front
and it will compile:
#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
According to 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.
One of the features of the std::bind
is what it does with extra arguments. Consider:
int f(int i) { return i + 1; }
auto bound_f = std::bind(f, 42);
bound_f()
invokes f(42)
which gives 43
. But it is also the case that bound_f("hello")
and bound_f(2.0, '3', std::vector{4, 5, 6})
gives you 43
. All arguments on the call site that don't have an associated placeholder are ignored.
The significance here is that is_invocable<decltype(bound_f), Args...>
is true for all sets of types Args...
Getting back to your example:
std::variant<std::function<void()>, std::function<void(int)>> v =
std::bind([]() noexcept {});
The bind on the right works a lot like bound_f
earlier. It's invocable with any set of arguments. It is invocable with no arguments (i.e. it is convertible to std::function<void()>
) and it is invocable with an int
(i.e. it is convertible to std::function<void(int)>
). That is, both alternatives of the variant can be constructed from the bind expression, and we have no way of distinguishing one from the other. They're both just conversions. Hence, ambiguous.
We would not have this problem with lambdas:
std::variant<std::function<void()>, std::function<void(int)>> v =
[]() noexcept {};
This works fine, because that lambda is only invocable with no arguments, so only one alternative is viable. Lambdas don't just drop unused arguments.
This generalizes to:
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);
});
}
Though if you want to actually pass placeholders here, this won't work. It really depends on your larger design what the right solution here might be.