【问题标题】:c++: How to write a std::bind-like object that checks for superfulous parameters?c++:如何编写一个类似 std::bind 的对象来检查多余的参数?
【发布时间】:2014-02-13 18:43:44
【问题描述】:

根据http://en.cppreference.com/w/cpp/utility/functional/bind,对于std::bind

成员函数 operator()

...

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

引用例子,可以做到:

void f(int n1, int n2, int n3, const int& n4, int n5) {
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int main() {
    auto f1 = std::bind(f, _2, _1, 42, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
}

如果您将f1 视为具有5 个参数的函数,其中3 个是固定的,那么根据常识,您应该无法使用3 个参数调用f1。但是,如上面的代码所示,您可以这样做,因为额外的参数会被忽略。

根据Why do objects returned from bind ignore extra arguments?的说法,这种行为似乎存在是因为实现起来很方便??

对我来说,这是一个相当令人困惑的库功能,因为它弄乱了函数的数量(例如,上面示例中的 5 - 3 = 3)并破坏了函数调用的推理。我想知道是否有任何实际用例使这种行为实际上是有益的?

更重要的是,是否可以实现std::bind 的变体来禁止这种行为?这里有哪些可能和困难。

谢谢

【问题讨论】:

  • 为什么不直接使用 std::function 呢?
  • std::bindstd::function 上运行,但不是std::function。 IMO,bind 就像函数的惰性部分应用程序,具有可能的参数切换和这种额外的(容易出错的)行为。
  • "如果您将上面的绑定结果 f1 视为函数或转换为 std::function,您将无法使用错误数量的参数调用 f1。"错误的。如果您将其绑定或转换为function&lt;void(int, int, int)&gt;,您将能够使用三个参数调用它,就像您的示例一样。
  • 完善/更正了陈述,以根据常识陈述我的观点。
  • 我的strict_binder 有一个函数模板operator(),即它有一个(潜在的)重载集operator()s。 -- 重写strict_bind 来调用函数而不是返回一个活页夹如果没有占位符参数 是可能且非常简单的:coliru.stacked-crooked.com/a/35b66c5b59050f1c

标签: c++ c++11 stdbind


【解决方案1】:

注意:限制参数的数量会破坏活页夹的一项功能,如 here 所示。

我的解决方案是基于计算通过的占位符确定使用的最大占位符。感谢Xeo 指出这个错误。

#include <functional>
#include <type_traits>
#include <utility>

template<class T, class U>
constexpr auto c_max(T&& t, U&& u)
-> typename std::remove_reference<decltype( t > u ? t : u )>::type
{  return t > u ? t : u;  }

template<class...>
struct max_placeholder : std::integral_constant<int, 0> {};

template<class T, class... Rest>
struct max_placeholder<T, Rest...>
: std::integral_constant<int, c_max(std::is_placeholder<T>::value,
                                    max_placeholder<Rest...>::value)>
{};

这增加了正确计算活页夹用户数量的负担。对于某些绑定的 Callable,例如函数指针,可以推断出参数的数量(这也允许自动提供所需数量的占位符)。一旦你确定了参数的数量,就很容易编写一个包装器来存储一个活页夹并提供一个operator() 模板来检查参数的数量:

template<class T, int N>
struct strict_binder
{
    T binder;

    template<class... Args>
    auto operator()(Args&&... args)
    -> decltype( binder(std::forward<Args>(args)...) )
    {
        static_assert(sizeof...(args) == N, "wrong number of arguments");
        return binder(std::forward<Args>(args)...);
    }
};

也可能产生替换失败而不是错误。

由于strict_binderbinder,您可以通过部分特化来表达这个概念:

namespace std
{
    template<class T, int N>
    struct is_bind_expression< strict_binder<T, N> >
        : public true_type
    {};
}

剩下的就是编写一个生成strict_binders 的函数模板。这是一个类似于std::bind的版本:

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> strict_binder<
       typename std::decay<
            decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
       >::type,
       max_placeholder<typename std::remove_reference<Args>::type...>::value
   >
{
    return { std::bind(std::forward<F>(f), std::forward<Args>(args)...) };
}

本质上,返回类型是a

strict_binder<decltype(std::bind(f, args...)), count_placeholders<Args...>::value>

strict_binder 存储std::bind 的结果类型。


你也可以编写一个类似apply的函数,在没有传递占位符时调用绑定函数:

template<int N, class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, N>, F&& f, Args&&... args)
-> strict_binder<
       typename std::decay<
            decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
       >::type,
       N
   >
{
    return { std::bind( std::forward<F>(f), std::forward<Args>(args)... ) };
}

template<class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, 0>, F&& f, Args&&... args)
-> decltype( std::bind( std::forward<F>(f), std::forward<Args>(args)... ) () )
{
    return std::bind( std::forward<F>(f), std::forward<Args>(args)... ) ();
}

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> decltype( strict_bind_or_call( std::integral_constant<int, max_placeholder<typename std::remove_reference<Args>::type...>::value>{},
                                  std::forward<F>(f), std::forward<Args>(args)... ) )
{
    using max_placeholder_here =
        max_placeholder<typename std::remove_reference<Args>::type...>;

    return strict_bind_or_call( max_placeholder_here{},
                                std::forward<F>(f), std::forward<Args>(args)... );
}

这使用标签调度来返回一个活页夹或调用函数的结果。 我放弃了正确格式化,您可能想在 detail 命名空间中引入别名模板。

注意strict_bind_or_call 的第二个重载中的decltype( std::bind(..) () ) 是重现INVOKE / bind 语义的简单方法;我不能只写f(args...),因为f 可能是一个成员函数。


使用示例:

#include <iostream>

void foo(int p0, int p1)
{ std::cout << "[" << p0 << ", " << p1 << "]\n"; }

int main()
{
    auto f0 = strict_bind(foo, std::placeholders::_1, 42);
    f0(1);

    strict_bind(foo, 1, 2);
}

【讨论】:

  • 请注意,is_placeholder 不只是返回 0 或 1,因此对于 count_placeholders&lt;_1_t, _2_t, _3_t&gt;,您将得到 6。您也不需要占位符的数量,而是最高占位符的值,即max 元函数。如果最高占位符是_3,那么你需要3个参数,即使没有_1_2
  • @Xeo 谢谢!我认为它现在已修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-29
  • 2016-08-27
相关资源
最近更新 更多