在 std::函数上递归应用 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)
.
在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;
}
目前为止,一切都好。
现在假设我想做同样的事情 - 绑定函数的第一个参数,取回新函数并重复此过程,直到绑定所有参数 - 但将其推广为不仅适用于上面C++示例中的 3 个参数的函数,而且适用于参数数量未知的函数*。
* 在C++中存在可变参数之类的东西,在 C++11 中存在可变参数模板。我在这里指的是可变参数模板。
基本上,我希望能够做的是编写一个接受任何std::function
的函数,并将第一个参数递归绑定到某个值,直到所有参数都被绑定并且可以调用该函数。
为简单起见,我们假设 std::function
表示一个接受任何积分参数并返回 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::placeholders
std::bind
。它们是必需的,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 个参数等相同。更不用说我的目标是对参数的数量没有任何硬编码限制。所以这是不可接受的。我不希望它有限制。
我需要找到一种方法来使可变参数模板代码(第二个代码片段(工作。
如果只有std::bind
不需要指定占位符 - 一切都会起作用,但std::bind
目前有效,我们需要找到某种方法来指定正确数量的占位符。
知道我们可以找到正确数量的占位符来指定 C++11 的sizeof...
可能会很有用
sizeof...(Tail)
但我无法从这个事实中得到任何有价值的东西。
首先,停止使用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);
}
接下来,仅当必须执行以下操作时,才键入擦除:
// `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;}
传递为第三个参数。
如果您不想更改,请将[](auto&&x){return x;}
传递为第 3 个参数。
这些代码都没有编译,因此可能存在拼写错误。 我也担心 C++14 返回类型扣除的递归,这有时会变得棘手。
如果你真的需要使用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)>());
}
演示。 make_integer_sequence
和朋友是C++14,但可以在C++11中轻松实现。
如果您准备放弃std::bind
(在我看来,对于 C++11 之前的部分应用程序来说,这确实是一个有点笨拙的解决方法(,这可以非常简洁地编写:
#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 进行测试。也在 ideone 上。
你不需要递归地使用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; });
现场演示
正如@T.C.在他的回答中指出的那样std::make_index_sequence
这是一个C++14功能,但它可以在C++11中实现。
- 使用 std::应用于 std::bind
- PCL:当我在setConditionFunction中使用std::bind 时,没有合适的转换函数
- std::bind on statd::array 的运算符 []
- std::bind c++ in if statement
- std::bind 是否实现了 std::ref 和 std::cref 来消除函数调用的歧义?
- C++ 事件管理器的回调,使用 std::function 和 std:bind 以及派生类作为参数
- 试图克服 std::bind 编译错误
- c++ std::bind within function
- std::bind to void* to std::function
- std::bind 和 std::函数术语不值为接受 0 个参数?
- 在调用 std::bind 的产品后意外调用析构函数
- 使用 object 中的方法调用带有 std::bind 和 std::function.target 的 C 样式函数
- 我们应该在使用 std::bind 应用之前检查一个不为空的函数吗?
- 为什么 std::bind 静态类型检查传递给函数的参数?
- 对函数库中的语法感到困惑 std::bind
- 在模板类成员上使用 std::bind
- 如何将参数从函数传递给 std::bind
- 如何在C++中使用 std::bind 函数作为信号处理程序?
- std::bind() 参数列表中函子的执行顺序(可能与函数参数的求值顺序无关)
- 在模板函数参数中使用 std::bind