首页 文章

在std :: function上递归应用std :: bind的问题

提问于
浏览
6

给定函数 f(x, y, z) ,我们可以将 x 绑定到0,获取函数 g(y, z) == f(0, y, z) . 我们可以继续这样做并获得 h() = f(0, 1, 2) .

在C语法中将是

#include <functional>
#include <iostream>

void foo(int a, long b, short c)
{
    std::cout << a << b << c << std::endl;
}

int main()
{
    std::function<void(int, long, short)> bar1 = foo;
    std::function<void(long, short)> bar2 = std::bind(bar1, 0, std::placeholders::_1, std::placeholders::_2);
    std::function<void(short)> bar3 = std::bind(bar2, 1, std::placeholders::_1);
    std::function<void()> bar4 = std::bind(bar3, 2);

    bar4(); // prints "012"

    return 0;
}

到现在为止还挺好 .

现在说我想做同样的事情 - 绑定一个函数的第一个参数,获取新函数并重复这个过程直到所有参数都被绑定 - 但是将它推广到不仅用于3个参数的函数,如上面的C示例,但是具有未知*参数数量的函数 .

*在C中存在可变参数,在C 11中存在可变参数模板 . 我在这里指的是可变参数模板 .

基本上,我希望能够做的是编写一个接受任何 std::function 的函数,并递归地将第一个参数绑定到某个值,直到所有参数都被绑定并且可以调用该函数 .

为简单起见,我们假设 std::function 表示一个函数接受任何integral参数并返回void .

该代码可以考虑为先前代码的概括

#include <functional>
#include <iostream>

// terminating case of recursion
void apply(std::function<void()> fun, int i)
{
    fun();
}

template<class Head, class... Tail>
void apply(std::function<void(Head, Tail...)> f, int i)
{
    std::function<void(Tail...)> g = std::bind(f, i);
    apply<Tail...>(g, ++i);
}

void foo(int a, long b, short c)
{
    std::cout << a << b << c << std::endl;
}

int main()
{
    std::function<void(int, long, short)> bar1 = foo;
    apply<int, long, short>(bar1, 0);

    return 0;
}

这段代码很棒 . 这正是我想要的 . 它不编译 .

main.cpp: In instantiation of 'void apply(std::function<void(Head, Tail ...)>, int) [with Head = int; Tail = {long int, short int}]':
main.cpp:24:40:   required from here
main.cpp:12:56: error: conversion from 'std::_Bind_helper<false, std::function<void(int, long int, short int)>&, int&>::type {aka std::_Bind<std::function<void(int, long int, short int)>(int)>}' to non-scalar type 'std::function<void(long int, short int)>' requested                        
      std::function<void(Tail...)> g = std::bind(f, i);
                                                     ^

问题是你不能在 std::bind 这样的电话中忽略 std::placeholders . 它们是必需的, std::bind 中的占位符数应与函数中非绑定参数的数量相匹配 .

如果我们改变线

std::function<void(Tail...)> g = std::bind(f, i);

std::function<void(Tail...)> g = std::bind(f, i, std::placeholders::_1, std::placeholders::_2);

我们看到它成功通过第一次 apply() 调用,但在第二次传递时卡住,因为在第二次传递期间 g 只需要一个占位符,而我们在 std::bind 中仍然有两个占位符 .

main.cpp: In instantiation of 'void apply(std::function<void(Head, Tail ...)>, int) [with Head = long int; Tail = {short int}]':
main.cpp:13:30:   required from 'void apply(std::function<void(Head, Tail ...)>, int) [with Head = int; Tail = {long int, short int}]'
main.cpp:24:40:   required from here
main.cpp:12:102: error: conversion from 'std::_Bind_helper<false, std::function<void(long int, short int)>&, int&, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type {aka std::_Bind<std::function<void(long int, short int)>(int, std::_Placeholder<1>, std::_Placeholder<2>)>}' to non-scalar type 'std::function<void(short int)>' requested
         std::function<void(Tail...)> g = std::bind(f, i, std::placeholders::_1, std::placeholders::_2);
                                                                                                      ^

有一种方法可以使用常规的非可变参数模板来解决这个问题,但它会对 std::function 可以拥有多少个参数进行限制 . 例如,仅当 std::function 具有3个或更少的参数时,此代码才有效

(替换前面代码中的 apply 函数)

// terminating case
void apply(std::function<void()> fun, int i)
{
    fun();
}

template<class T0>
void apply(std::function<void(T0)> f, int i)
{
    std::function<void()> g = std::bind(f, i);
    apply(g, ++i);
}

template<class T0, class T1>
void apply(std::function<void(T0, T1)> f, int i)
{
    std::function<void(T1)> g = std::bind(f, i, std::placeholders::_1);
    apply<T1>(g, ++i);
}

template<class T0, class T1, class T2>
void apply(std::function<void(T0, T1, T2)> f, int i)
{
    std::function<void(T1, T2)> g = std::bind(f, i, std::placeholders::_1, std::placeholders::_2);
    apply<T1, T2>(g, ++i);
}

但是该代码的问题在于我必须定义一个新的 apply 函数以支持带有4个参数的 std::function ,然后使用5个参数,6依旧相同 . 更不用说我的目标是不对参数的数量有任何硬编码限制 . 所以这是不可接受的 . 我不希望它有限制 .

I need to find a way to make the variadic template code (the second code snippet) to work.

如果只有 std::bind 不需要指定占位符 - 一切都会起作用,但是当 std::bind 当前有效时,我们需要找到一些方法来指定合适的占位符数 .

知道我们可以找到适当数量的占位符来指定C 11的 sizeof... 可能是有用的 .

sizeof...(Tail)

但是出于这个事实,我无法得到任何有 Value 的东西 .

4 回答

  • 0

    首先,除非你绝对需要,否则请停止使用 bind .

    // terminating case of recursion
    void apply(std::function<void()> fun, int i) {
      fun();
    }
    // recursive case:
    template<class Head, class... Tail>
    void apply(std::function<void(Head, Tail...)> f, int i) {
      // create a one-shot lambda that binds the first argument to `i`:
      auto g = [&](Tail&&...tail) // by universal ref trick, bit fancy
      { return std::move(f)(std::move(i), std::forward<Tail>(tail)...);};
      // recurse:
      apply<Tail...>(g, ++i);
    }
    

    接下来,如果必须,只需键入erase:

    // `std::resukt_of` has a design flaw.  `invoke` fixes it:
    template<class Sig,class=void>struct invoke{};
    template<class Sig>using invoke_t=typename invoke<Sig>::type;
    
    // converts any type to void.  Useful for sfinae, and may be in C++17:
    template<class>struct voider{using type=void;};
    template<class T>using void_t=typename voider<T>::type;
    
    // implementation of invoke, returns type of calling instance of F
    // with Args...
    template<class F,class...Args>
    struct invoke<F(Args...),
      void_t<decltype(std::declval<F>()(std::declval<Args>()...))>
    >{
      using type=decltype(std::declval<F>()(std::declval<Args>()...));
    };
    
    // tells you if F(Args...) is a valid expression:
    template<class Sig,class=void>struct can_invoke:std::false_type{};
    template<class Sig>
    struct can_invoke<Sig,void_t<invoke_t<Sig>>>
    :std::true_type{};
    

    现在我们有一些机器,一个基础案例:

    // if f() is a valid expression, terminate:
    template<class F, class T, class I,
      class=std::enable_if_t<can_invoke<F()>{}>
    >
    auto apply(F&& f, T&& t, I&&i)->invoke_t<F()>
    {
      return std::forward<F>(f)();
    }
    

    其中说“如果我们可以被调用,只需调用 f .

    接下来,递归案例 . 它依赖于C 14返回类型扣除:

    // if not, build lambda that binds first arg to t, then recurses
    // with i(t):
    template<class F, class T, class I,
      class=std::enable_if_t<!can_invoke<F()>{}, int>>
    >
    auto apply(F&& f, T&& t, I&&i)
    {
      // variardic auto lambda, C++14 feature, with sfinae support
      // only valid to call once, which is fine, and cannot leave local
      // scope:
      auto g=[&](auto&&...ts) // takes any number of params
      -> invoke_t< F( T, decltype(ts)... ) > // sfinae
      {
        return std::forward<F>(f)(std::forward<T>(t), decltype(ts)(ts)...);
      };
      // recurse:
      return apply(std::move(g), i(t), std::forward<I>(i));
    }
    

    如果你想增加,请传递 [](auto&&x){return x+1;} 作为第3个arg .

    如果您不想更改,请将 [](auto&&x){return x;} 传递给第3个arg .

    这些代码都没有被编译,因此可能存在拼写错误 . 我也担心C 14返回类型演绎的递归递归,有时会变得棘手 .

  • 1

    如果你真的必须使用 bind ,你可以通过专门化 std::is_placeholder 来定义你自己的占位符类型:

    template<int N>
    struct my_placeholder { static my_placeholder ph; };
    
    template<int N>
    my_placeholder<N> my_placeholder<N>::ph;
    
    namespace std {
        template<int N>
        struct is_placeholder<::my_placeholder<N>> : std::integral_constant<int, N> { };
    }
    

    这有用的原因是它允许您在编译时将整数映射到占位符,您可以使用 integer_sequence 技巧:

    void apply(std::function<void()> fun, int i)
    {
        fun();
    }
    template<class T, class... Ts>
    void apply(std::function<void(T, Ts...)> f, int i);
    
    template<class T, class... Ts, int... Is>
    void apply(std::function<void(T, Ts...)> f, int i, std::integer_sequence<int, Is...>)
    {
        std::function<void(Ts...)> g = std::bind(f, i, my_placeholder<Is + 1>::ph...);
        apply(g, ++i);
    }
    
    template<class T, class... Ts>
    void apply(std::function<void(T, Ts...)> f, int i) {
        apply(f, i, std::make_integer_sequence<int, sizeof...(Ts)>());
    }
    

    Demo . make_integer_sequence 和朋友是C 14,但可以在C 11中轻松实现 .

  • 4

    如果您准备放弃 std::bind (在我看来,这对于C11之前的部分应用程序来说真的是一个hacky变通方法),这可以非常简洁地写出来:

    #include <functional>
    #include <iostream>
    
    // End recursion if no more arguments
    void apply(std::function<void()> f, int) {
      f();
    }
    
    template <typename Head, typename ...Tail>
    void apply(std::function<void(Head, Tail...)> f, int i=0) {
      auto g = [=](Tail&& ...args){
        f(i, std::forward<Tail>(args)...);
      };
    
      apply(std::function<void(Tail...)>{g}, ++i);
    }
    
    void foo(int a, int b, int c, int d) {
      std::cout << a << b << c << d << "\n";
    }
    
    int main() {
      auto f = std::function<void(int,int,int,int)>(foo);
      apply(f);
    }
    

    在C 11模式下使用clang 3.4和g 4.8.2进行测试 . 还on ideone .

  • 2

    您不需要递归地使用 std::bind 来调用具有参数元组的某个函数,可以使用参数索引来评估这些值:

    #include <functional>
    #include <utility>
    
    template <typename... Types, std::size_t... indexes,  typename Functor>
    void apply(std::function<void(Types...)> f, std::index_sequence<indexes...>, Functor&& functor)
    {
        f(static_cast<Types>(std::forward<Functor>(functor)(indexes))...);
    }
    
    template <typename... Types, typename Functor>
    void apply(std::function<void(Types...)> f, Functor&& functor)
    {
        apply(f, std::make_index_sequence<sizeof...(Types)>{}, std::forward<Functor>(functor));
    }
    

    使用示例:

    void foo(int a, long b, short c)
    {
        std::cout << a << b << c << std::endl;
    }
    
    // ...
    
    std::function<void(int, long, short)> bar = foo;
    
    apply(bar, [](std::size_t index){ return (int)index; });
    

    Live demo

    正如@T.C.所述in his answer std::make_index_sequence 是C 14特征,但it can be implemented in C++11 .

相关问题