簡體   English   中英

在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)

用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表示一個帶有任何整數參數並返回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依舊相同。 更不用說我的目標是不對參數的數量有任何硬編碼限制。 所以這是不可接受的。 我不希望它有限制。

我需要找到一種方法來使可變參數模板代碼(第二個代碼片段)工作。

如果只有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);
}

接下來,如果必須,只需鍵入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返回類型演繹的遞歸,有時會變得棘手。

如果你真的必須使用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部分應用程序來說這實際上是一個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。 也在想法上

你不需要遞歸地使用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; });

現場演示

正如@ TC 在他的回答中指出的那樣 std::make_index_sequence是一個C ++ 14特性,但它可以在C ++ 11中實現

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM