问题
I have problem similar to Passing different lambdas to function template in c++ but now with wrappers created by std::bind
instead of lambdas.
I have two overloads of method Add
that take different forms of std::function
:
template<typename T>
struct Value
{
T value;
};
template <typename T>
void Add(Value<T> &value, function<bool()> predicate)
{
}
template <typename T>
void Add(Value<T> &value, block_deduction<function<bool(const Value<T> &)>> predicate)
{
}
This now works fine with lambdas but fails with functors bound with std::bind
:
struct Predicates
{
bool Predicate0() { return true; }
bool Predicate1(const Value<int> &) { return true; }
};
Predicates p;
Add(i, std::bind(&Predicates::Predicate0, &p));
fails with
error C2668: 'Add': ambiguous call to overloaded function
and
Add(i, std::bind(&Predicates::Predicate1, &p, _1));
fails with static assert (Visual C++ 2015, Update 3):
tuple index out of bounds
Is there a way to make it work both with lambdas and bound functors? I would think of using SFINAE to enable the individual overload based on is_bindable_expression
and checking the argument type but I'm failing to put it together.
回答1:
Stop using std::bind
. It is a mess of random features and quirks.
Todays quirk is that std::bind
will accept an unlimited number of arguments and discard any extra ones. Tomorrow you might run into the fact that passing std::bind
result to std::bind
does strange magic.
std::bind
was ported over to boost
at the same time lambdas where added to the language. Lambdas solve almost every problem bind
does in just as clear syntax and fails to have the myraid of quirks bind
does, especially post C++14 when auto
lambdas are available. (Most C++11 compilers also supported auto
lambda).
You can write functions so that one or the other is the preferred overload when they both apply. But doing so adds a pile of noise to your interface, and in this case about the only reason why you'd want that preference is because std::bind
is doing something stupid.
Engineering around a poorly designed bit of std
library is not worth it. Simply stop using that poorly designed bit of std
library, or at point of use cast explicitly.
Failing that, do this:
template <class T, class F,
std::enable_if_t<
std::is_convertible<
std::result_of_t<std::decay_t<F> const&(Value<T> const&)>,
bool
>{}, int
> = 0
>
void Add(Value<T> &value, F&& f)
{
// do pass f Value<T>
}
template <class T, class F,
std::enable_if_t<
!std::is_convertible<
std::result_of_t<std::decay_t<F> const&(Value<T> const&)>,
bool
>{}
&& std::is_convertible<
std::result_of_t<std::decay_t<F> const&()>,
bool
>{}, int
> = 0
>
void Add(Value<T> &value, F&& f)
{
// do not pass f Value<T>
}
where we throw some nasty SFINAE detection on which of the two overloads you want to use, and explicitly prefer one.
This is not worth it.
回答2:
I don't think you can do what you want.
You can use is_bind_expression
to check if your argument is a type produced by a call to std::bind
, but there is no way to tell how many arguments the callable expects. As cpplearned mentioned in the comments, this is a feature of std::bind:
If some of the arguments that are supplied in the call to
g()
are not matched by any placeholders stored ing
, the unused arguments are evaluated and discarded.
That means that both overloads are equally valid.
If you don't mind sharing the same overload for all bind
results, you can pass all the parameters and let them be discarded at will:
template <typename T>
void AddImpl(Value<T> &value, function<bool()> predicate, std::false_type)
{
predicate();
}
template <typename T>
void AddImpl(Value<T> &value, block_deduction<function<bool(const Value<T> &)>> predicate, std::false_type)
{
predicate(value);
}
template <typename T, typename U>
void AddImpl(Value<T>& value, U&& bind_expression, std::true_type)
{
bind_expression(value);
}
template<typename T, typename U>
void Add(T&& t, U&& u)
{
AddImpl(std::forward<T>(t), std::forward<U>(u), std::is_bind_expression<std::decay_t<U>>{});
}
demo
But this is similar to using boolean parameters. In my opinion it'd be better for readability to dispatch on properly named tags:
template <typename T>
void AddImpl(Value<T> &value, function<bool()> predicate, tag::default_)
{
predicate();
}
template <typename T>
void AddImpl(Value<T> &value, block_deduction<function<bool(const Value<T> &)>> predicate, tag::default_)
{
predicate(value);
}
template <typename T, typename U>
void AddImpl(Value<T>& value, U&& bind_expression, tag::bind)
{
bind_expression(value);
}
template<typename T, typename U>
void Add(T&& t, U&& u)
{
AddImpl(std::forward<T>(t), std::forward<U>(u), tag::get_tag<std::decay_t<U>>{});
}
with tags defined as
namespace tag
{
struct default_{};
struct bind{};
template<typename T, typename = void>
struct get_tag : default_ {};
template<typename T>
struct get_tag<T, std::enable_if_t<std::is_bind_expression<T>::value>> : bind {};
}
demo
来源:https://stackoverflow.com/questions/40547846/passing-result-of-stdbind-to-stdfunction-overloads