【问题标题】:Passing result of std::bind to std::function "overloads"将 std::bind 的结果传递给 std::function “重载”
【发布时间】:2016-11-11 12:17:10
【问题描述】:

我有类似于 Passing different lambdas to function template in c++ 的问题,但现在使用由 std::bind 而不是 lambdas 创建的包装器。

我有两个方法 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));

失败

错误 C2668:“添加”:对重载函数的模糊调用

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

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

元组索引超出范围

有没有办法让它与 lambdas 和绑定函子一起工作?我会考虑使用 SFINAE 来启用基于 is_bindable_expression 的单个重载并检查参数类型,但我没有把它放在一起。

【问题讨论】:

  • std::bind(&amp;Predicates::Predicate0, &amp;p)()std::bind(&amp;Predicates::Predicate0, &amp;p)(Value&lt;int&gt;{}) 均有效。 (在后一种情况下,参数Value&lt;int&gt;{} 被忽略。)这是std::bind 的一个特性。
  • 你在滥用超载。满足“可以用这个参数调用它的实例”或“可以用另一个参数调用它的实例”属性的类型的交集是非空的。

标签: c++ c++14 template-meta-programming sfinae


【解决方案1】:

停止使用std::bind。这是一堆随机特征和怪癖。

今天的怪癖是std::bind 将接受无限数量的参数并丢弃任何多余的参数。明天你可能会遇到这样一个事实:将std::bind 结果传递给std::bind 会产生奇怪的魔力。

std::bind 被移植到boost,同时将 lambdas 添加到语言中。 Lambda 几乎解决了 bind 的所有问题,语法清晰,但没有 bind 的无数怪癖,尤其是在 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 检测,并明确首选一个。

这不值得。

【讨论】:

  • +1 表示不鼓励使用 std::bind,尤其是因为我们有 C++14 lambda,而 std::bind 实际上是 boost 中那个更穷的兄弟。我试图想出一个比 lambda 更喜欢 bind 的理由,但我想出的唯一一个半好的理由是 no-type-erasure,static-return-type 用于创建事件处理程序序列,但这并不意味着什么如果你不能拼写那种类型。
  • @krzaq 在某些情况下,内联头文件中的 lambda-returning 函数存在 ODR 问题。我希望 C++ 标准委员会能够处理它们,它们不会触发(几乎总是完全无害的)UB。
  • @Yakk 我不知道std::bind 的问题,我以后会尽量避免。尽管如此,我尝试了您讨厌的 SFINAE 检测,但它似乎无论如何都不起作用。在 VC++ 中,我在使用 STL 中的静态断言“元组索引越界”和 GCC 编译它时遇到了麻烦,虽然它编译得很好,但结果不是我所期望的。似乎使用不同绑定方法对Add 的两次调用最终都会调用相同的重载。见melpon.org/wandbox/permlink/pcwxYAQcaoSxFQNi
  • @manison 尚无 MSVC 版本完全支持 C++11;他们最大的问题在于SFINAE问题。询问有关 MSVC 版本特定解决方法的 SO 问题可能会得到答案,但有时不会。这就是我对 Gcc 的期望,因为明确地绑定将采用额外的参数并丢弃它们。这就是绑定所做的。当您调用 bind 时,这就是您要求它执行的操作。传递额外的参数似乎是有害的,所以我就是这样做的。
【解决方案2】:

我不认为你可以做你想做的事。

您可以使用is_bind_expression 来检查您的参数是否是通过调用std::bind 生成的类型,但是无法判断可调用对象需要多少个参数。正如cpplearned在cmets中提到的,这是std::bind的一个特性:

如果在调用 g() 时提供的某些参数不是 由存储在g 中的任何占位符匹配,未使用的参数是 评估并丢弃。

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


如果您不介意为所有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

【讨论】:

  • 然后我可以利用这个特性并且只有一个方法Addfunction 接受参数:模板 void Add(Value &value, block_deduction &)>> 谓词);并始终使用参数调用predicate。如果参数与绑定函子中的占位符不匹配,则该参数将被忽略。然而,它与 lambdas 不同。这是一个好的 C++ 解决方案吗?你有什么建议?
  • 我不知道你的用例,它可能是正确的,但也可能是过度工程。话虽这么说,如果你可以放弃这个论点,我会在我回家(2-3 小时)时举一个例子
  • @manison 检查答案。希望这对您有所帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
相关资源
最近更新 更多