Passing result of std::bind to std::function “overloads”

微笑、不失礼 提交于 2021-02-08 05:17:42

问题


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 in g, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!