将 std::bind 的结果传递给 std::function "overloads"

Passing result of std::bind to std::function "overloads"

我有类似 的问题,但现在使用 std::bind 而不是 lambda 创建的包装器。

我有两个重载方法 Add,它们采用不同形式的 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)
{
}

现在这对 lambdas 可以正常工作,但对与 std::bind:

绑定的仿函数会失败
struct Predicates
{
    bool Predicate0() { return true; }
    bool Predicate1(const Value<int> &) { return true; }
};

Predicates p;
Add(i, std::bind(&Predicates::Predicate0, &p));

失败

error C2668: 'Add': ambiguous call to overloaded function

Add(i, std::bind(&Predicates::Predicate1, &p, _1));

静态断言失败(Visual C++ 2015,更新 3):

tuple index out of bounds

有没有办法让它同时适用于 lambda 和绑定仿函数?我会考虑使用 SFINAE 启用基于 is_bindable_expression 的单独重载并检查参数类型,但我无法将它们放在一起。

我不认为你可以为所欲为。

您可以使用 is_bind_expression 来检查您的参数是否是由调用 std::bind 生成的类型,但是无法判断可调用对象需要多少个参数。正如 cpplearned 在评论中提到的,这是 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.

这意味着两个重载同样有效。


如果您不介意为所有 bind 结果共享相同的重载,您可以传递所有参数并让它们随意丢弃:

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

但这类似于使用布尔参数。在我看来,在正确命名的标签上分派可读性更好:

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>>{});
}

标签定义为

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

停止使用 std::bind。这是一堆乱七八糟的随机功能和怪癖。

今天的怪癖是 std::bind 将接受无限数量的参数并丢弃任何额外的参数。明天你可能 运行 了解到将 std::bind 结果传递给 std::bind 会产生奇怪的魔法。

std::bind 被移植到 boost,同时 lambdas 添加到语言中。 Lambda 解决了几乎所有问题 bind 用同样清晰的语法解决了几乎所有问题,并且没有 bind 做的无数怪癖,尤其是 post C++14 当 auto lambda 可用时. (大多数 C++11 编译器也支持 auto lambda)。

您可以编写函数,以便在两者都适用时将其中一个作为首选重载。但是这样做会给你的界面增加一堆噪音,在这种情况下,你想要这种偏好的唯一原因是因为 std::bind 正在做一些愚蠢的事情。

围绕 std 库中设计不佳的部分进行工程设计是不值得的。只需停止使用 std 库中设计不佳的部分,或在使用时显式转换。


做不到这一点,这样做:

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>
}

我们对您想使用的两个重载中的哪一个进行一些讨厌的 SFINAE 检测,并明确更喜欢一个。

这不值得。