简体   繁体   English

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

[英]Issues applying std::bind recursively on a std::function

Given a function f(x, y, z) we can bind x to 0, getting a function g(y, z) == f(0, y, z) . 给定函数f(x, y, z)我们可以将x绑定到0,得到函数g(y, z) == f(0, y, z) We can continue doing this and get h() = f(0, 1, 2) . 我们可以继续这样做并得到h() = f(0, 1, 2)

In C++ syntax that would be 用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;
}

So far so good. 到现在为止还挺好。

Now say that I want to do the same -- bind the first argument of a function, get the new function back and repeat this process until all arguments are binded -- but generalize it to work not only with a function of 3 arguments as in the C++ example above, but with a function with unknown* number of arguments. 现在说我想做同样的事情 - 绑定一个函数的第一个参数,获取新函数并重复这个过程直到所有参数都被绑定 - 但是将它推广到不仅用于3个参数的函数,如上面的C ++示例,但是具有未知*参数数量的函数。

* In C++ there is such thing as variadic arguments and in C++11 there are variadic templates. *在C ++中存在可变参数,在C ++ 11中存在可变参数模板。 I'm referring to variadic templates here. 我在这里指的是可变参数模板。

Basically, what I want to be able to do, is to write a function that accepts any std::function and recursively binds the first argument to some value until all arguments are binded and the function can be called. 基本上,我希望能够做的是编写一个接受任何std::function并递归地将第一个参数绑定到某个值,直到所有参数都被绑定并且可以调用该函数。

For the simplicity, let's assume that std::function represents a function taking any integral arguments and returning void. 为简单起见,我们假设std::function表示一个带有任何整数参数并返回void的函数。

This code can be considerate to be a generalization of the previous code 该代码可以考虑为先前代码的概括

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

This code is great. 这段代码很棒。 It is exactly what I want. 这正是我想要的。 It doesn't compile. 它不编译。

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);
                                                     ^  

The issue is that you can't just leave out std::placeholders in std::bind call like that. 问题是你不能像这样在std::bind调用中省略std::placeholders They are required, and number of placeholders in std::bind should match the number of non-binded arguments in the function. 它们是必需的, std::bind的占位符数应与函数中非绑定参数的数量相匹配。

If we change line 如果我们改变线

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

to

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

we see that it successfully passes through the first apply() call, but gets stuck on the second pass, because during the second pass g needs only one placeholder, while we still have two of them in the std::bind . 我们看到它成功通过第一次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);
                                                                                                      ^

There is a way to solve that using regular non-variadic templates, but it introduces a limit on how many arguments std::function can have. 有一种方法可以使用常规的非可变参数模板来解决这个问题,但是它引入了对std::function可以有多少参数的限制。 For example, this code works only if std::function has 3 or less arguments 例如,仅当std::function具有3个或更少的参数时,此代码才有效

(replace apply functions in the previous code on these) (替换上面代码中的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);
}

But the issue with that code is that I would have to define a new apply function to support std::function with 4 arguments, then the same with 5 arguments, 6 and so on. 但是该代码的问题在于我必须定义一个新的apply函数来支持带有4个参数的std::function ,然后使用5个参数,6依旧相同。 Not to mention that my goal was to not have any hard-coded limit on the number of arguments. 更不用说我的目标是不对参数的数量有任何硬编码限制。 So this is not acceptable. 所以这是不可接受的。 I don't want it to have a limit. 我不希望它有限制。

I need to find a way to make the variadic template code (the second code snippet) to work. 我需要找到一种方法来使可变参数模板代码(第二个代码片段)工作。

If only std::bind didn't require to specify placeholders -- everything would work, but as std::bind currently works, we need to find some way to specify the right number of placeholders. 如果只有std::bind不需要指定占位符 - 一切都会起作用,但是当std::bind当前有效时,我们需要找到一些方法来指定合适的占位符数。

It might be useful to know that we can find the right number of placeholders to specify with C++11's sizeof... 知道我们可以找到适当数量的占位符来指定C ++ 11的sizeof...

sizeof...(Tail)

but I couldn't get anything worthwhile out of this fact. 但是出于这个事实,我无法得到任何有价值的东西。

First, stop using bind unless you absolutely need to. 首先,除非你绝对需要,否则停止使用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);
}

next, only type erase if you have to: 接下来,如果必须,只需键入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{};

now we have some machinery, a base case: 现在我们有一些机器,一个基础案例:

// 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)();
}

which says "if we can be invoked, just invoke f . 其中说“如果我们可以被调用,只需调用f

Next, the recursive case. 接下来,递归案例。 It relies on C++14 return type deduction: 它依赖于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));
}

If you want increment, pass [](auto&&x){return x+1;} as 3rd arg. 如果你想增加,传递[](auto&&x){return x+1;}作为第3个arg。

If you want no change, pass [](auto&&x){return x;} as 3rd arg. 如果您不想更改,请将[](auto&&x){return x;}作为第3个arg传递。

None of this code has been compiled, so there may be typos. 这些代码都没有被编译,因此可能存在拼写错误。 I am also worried about the recursion of apply with C++14 return type deduction, that gets tricky sometimes. 我也担心使用C ++ 14返回类型演绎的递归,有时会变得棘手。

If you really have to use bind , you can define your own placeholder types by specializing std::is_placeholder : 如果你真的必须使用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> { };
}

The reason this is useful is that it allows you to then map an integer to a placeholder at compile time, which you can use with the integer_sequence trick: 这很有用的原因是它允许您在编译时将整数映射到占位符,您可以使用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 and friends are C++14, but can be implemented easily in C++11. make_integer_sequence和朋友是C ++ 14,但可以在C ++ 11中轻松实现。

If you're prepared to drop std::bind (which really was a bit of a hacky workaround for pre-C++11 partial applications in my view) this can be quite concisely written: 如果您准备删除std::bind (在我的视图中对于前C ++ 11部分应用程序来说这实际上是一个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);
}

Tested working with clang 3.4 and g++ 4.8.2 in C++11 mode. 测试在C ++ 11模式下使用clang 3.4和g ++ 4.8.2。 Also on ideone . 也在想法上

You don't need to use std::bind recursively to call some function with a tuple of parameters which values can be evaluated using parameter index: 你不需要递归地使用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));
}

Example of use: 使用示例:

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 现场演示

As @ TC noted in his answer std::make_index_sequence is a C++14 feature but it can be implemented in C++11 . 正如@ TC 在他的回答中指出的那样 std::make_index_sequence是一个C ++ 14特性,但它可以在C ++ 11中实现

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM