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

Joh*_*ger 6 c++ templates bind variadic-templates c++11

给定一个函数,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;
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

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

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

基本上,我希望能够做的是编写一个接受any的函数,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;
}
Run Code Online (Sandbox Code Playgroud)

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

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);
                                                     ^  
Run Code Online (Sandbox Code Playgroud)

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

如果我们改变线

std::function<void(Tail...)> g = std::bind(f, i);
Run Code Online (Sandbox Code Playgroud)

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

我们看到它成功通过第一次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);
                                                                                                      ^
Run Code Online (Sandbox Code Playgroud)

有一种方法可以使用常规的非可变参数模板来解决这个问题,但是它引入了对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);
}
Run Code Online (Sandbox Code Playgroud)

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

我需要找到一种方法来使可变参数模板代码(第二个代码片段)工作.

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

知道我们可以找到使用C++ 11指定的正确数量的占位符可能会有用 sizeof...

sizeof...(Tail)
Run Code Online (Sandbox Code Playgroud)

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

Yak*_*ont 4

首先,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);
}
Run Code Online (Sandbox Code Playgroud)

接下来,仅在必须时键入擦除:

// `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{};
Run Code Online (Sandbox Code Playgroud)

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

// 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)();
}
Run Code Online (Sandbox Code Playgroud)

其中表示“如果我们可以被调用,只需调用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));
}
Run Code Online (Sandbox Code Playgroud)

如果您想要增量,请[](auto&&x){return x+1;}作为第三个参数传递。

如果您不想更改,请[](auto&&x){return x;}作为第三个参数传递。

这些代码都没有经过编译,因此可能存在拼写错误。我还担心 apply 与 C++14 返回类型推导的递归,有时会变得很棘手。