在std :: function上递归应用std :: bind的问题 [英] Issues applying std::bind recursively on a std::function
问题描述
给出一个函数f(x, y, z)
,我们可以将x
绑定到0,得到一个函数g(y, z) == f(0, y, z)
.我们可以继续这样做并获得h() = f(0, 1, 2)
.
Given a function f(x, y, z)
we can bind x
to 0, getting a function g(y, z) == f(0, y, z)
. We can continue doing this and get 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;
}
到目前为止一切都很好.
So far so good.
现在说我想做同样的事情-绑定一个函数的第一个参数,取回新函数并重复此过程,直到所有参数都被绑定-但将其推广到不仅适用于3的函数参数与上面的C ++示例一样,但是具有一个未知数量的函数.
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.
*在C ++中,存在可变参数,而在C ++ 11中则具有可变模板.我在这里指的是可变参数模板.
* In C++ there is such thing as variadic arguments and in C++11 there are variadic templates. I'm referring to variadic templates here.
基本上,我想做的是编写一个接受任何std::function
的函数,然后将第一个参数递归地绑定到某个值,直到所有参数都被绑定并且可以调用该函数为止.
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
代表一个函数,该函数接受任何 integral 参数并返回void
For the simplicity, let's assume that std::function
represents a function taking any integral arguments and returning 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);
^
问题在于,您不能像这样在std::bind
调用中留下std::placeholders
.它们是必需的,并且std::bind
中的占位符数量应与函数中未绑定参数的数量匹配.
The issue is that you can't just leave out std::placeholders
in std::bind
call like that. They are required, and number of placeholders in std::bind
should match the number of non-binded arguments in the function.
如果我们换行
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
中仍然有两个占位符
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
.
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个或更少参数的情况下有效
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. For example, this code works only if std::function
has 3 or less arguments
(在上面的代码中替换apply
函数)
(replace apply
functions in the previous code on these)
// 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个等等.更不用说我的目标是对参数数量不加任何硬编码限制.因此,这是不可接受的.我不希望它有限制.
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. 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.
如果仅std::bind
不需要指定占位符-一切正常,但是随着std::bind
当前起作用,我们需要找到某种方法来指定正确数量的占位符.
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.
知道我们可以找到合适数量的占位符来指定C ++ 11的sizeof...
It might be useful to know that we can find the right number of placeholders to specify with C++11's sizeof...
sizeof...(Tail)
但是由于这个事实,我什么都做不了.
but I couldn't get anything worthwhile out of this fact.
推荐答案
首先,除非绝对需要,否则请停止使用bind
.
First, stop using bind
unless you absolutely need to.
// 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:
// `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)();
}
表示如果可以被调用,只需调用f
.
which says "if we can be invoked, just invoke f
.
接下来,是递归的情况.它依赖于C ++ 14返回类型推导:
Next, the recursive case. It relies on C++14 return type deduction:
// 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;}
作为第三个参数传递.
If you want increment, pass [](auto&&x){return x+1;}
as 3rd arg.
如果您不希望更改,请将[](auto&&x){return x;}
作为第三个参数传递.
If you want no change, pass [](auto&&x){return x;}
as 3rd arg.
此代码均未编译,因此可能存在拼写错误.我还担心C ++ 14返回类型推导的apply递归,有时会变得棘手.
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.
这篇关于在std :: function上递归应用std :: bind的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!