2015-02-15 15 views
6

Data una funzione f(x, y, z) è possibile associare x a 0, ottenendo una funzione g(y, z) == f(0, y, z). Possiamo continuare a farlo e ottenere h() = f(0, 1, 2).Problemi con l'applicazione std :: binding ricorsivo su una std :: funzione

In C++ sintassi che sarebbe

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

Fin qui tutto bene.

Ora dire che voglio fare lo stesso: associare il primo argomento di una funzione, richiamare la nuova funzione e ripetere questo processo finché tutti gli argomenti non sono vincolati, ma generalizzarlo per funzionare non solo con una funzione di 3 argomenti come nell'esempio C++ sopra, ma con una funzione con numero sconosciuto * di argomenti.

* In C++ esistono argomenti variadici e in C++ 11 ci sono modelli variadici. Mi riferisco ai modelli variadici qui.

Fondamentalmente, ciò che voglio essere in grado di fare, è scrivere una funzione che accetta qualsiasi std::function e ricorsivamente associa il primo argomento a qualche valore finché tutti gli argomenti non sono vincolati e la funzione può essere chiamata.

Per la semplicità, supponiamo che std::function rappresenti una funzione che accetta qualsiasi argomento integral e restituisce vuoto.

Questo codice può essere considerate come una generalizzazione del codice precedente

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

Questo codice è grande. È esattamente quello che voglio Non viene compilato.

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

Il problema è che non si può semplicemente lasciare fuori std::placeholders in std::bind chiamata così. Sono obbligatori e il numero di segnaposto in std::bind deve corrispondere al numero di argomenti non vincolati nella funzione.

Se cambiamo linea

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

a

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

vediamo che passa con successo attraverso il primo apply() chiamata, ma si blocca al secondo passaggio, perché durante il secondo passaggio g esigenze solo un segnaposto, mentre ne abbiamo ancora due nello 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); 
                            ^

C'è un modo per risolvere questo utilizzando i modelli non variadic regolari, ma introduce un limite al numero di argomenti std::function può avere. Ad esempio, questo codice funziona solo se std::function ha 3 o meno argomenti

(sostituire apply funzioni nel codice precedente su questi)

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

Ma il problema con tale codice è che avrei definire un nuova funzione apply per supportare std::function con 4 argomenti, quindi la stessa cosa con 5 argomenti, 6 e così via.Senza contare che il mio obiettivo era di non avere limiti hard-coded sul numero di argomenti. Quindi questo non è accettabile. Non voglio che abbia un limite.

Ho bisogno di trovare un modo per far funzionare il codice del modello variadic (il secondo frammento di codice).

Se solo std::bind non ha richiesto di specificare i segnaposto - tutto avrebbe funzionato, ma come std::bind opere attualmente, abbiamo bisogno di trovare un modo per specificare il giusto numero di segnaposto.

Potrebbe essere utile sapere che siamo in grado di trovare il giusto numero di segnaposto per specificare con C++ 11 del sizeof...

sizeof...(Tail) 

ma non ho potuto ottenere qualcosa di buono da questo fatto.

+1

Il mio primo consiglio è di smettere di usare 'std :: bind' a meno che non sia assolutamente necessario. – Yakk

+0

C++ ora è C++ 11. – Puppy

+0

@Puppy Non consideri nemmeno C++ 14? –

risposta

4

Per prima cosa, smettere di usare bind a meno che non sia assolutamente necessario.

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

prossimo, unico tipo cancellare se si deve:

// `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{}; 

ora abbiamo alcuni macchinari, un caso base:

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

che dice "se ci può essere invocato, invoca semplicemente f.

Successivamente, il caso ricorsivo si basa sulla deduzione del tipo di ritorno 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)); 
} 

Se si desidera aumentare, passare [](auto&&x){return x+1;} come 3 ° arg.

Se non si desidera modificare, passare [](auto&&x){return x;} come 3 ° arg.

Nessuno di questo codice è stato compilato, quindi potrebbero esserci errori di battitura. Sono anche preoccupato per la ricorsione di applicare con deduzione del tipo di ritorno C++ 14, che a volte diventa complicato.

+0

Sarebbe bello se qualcuno potesse rinforzare il codice con alcuni commenti –

+0

Qual è il terzo argomento 'I && i'? Come deve essere chiamato "apply"? – 0x499602D2

+0

@ 0x49 Dichiaro che cosa fa il terzo arg dopo il codice. Cosa non è chiaro? – Yakk

1

Se siete pronti a cadere std::bind (che in realtà era un po 'una soluzione hacky per la pre-C++ 11 applicazioni parziali nel mio punto di vista) questo può essere molto conciso scritto:

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

Provato lavorare con clang 3.4 e g ++ 4.8.2 in modalità C++ 11. Anche on ideone.

+0

'&' invece di '=' in lambda cattura in modo sicuro e più efficiente ('=' provoca un'assegnazione di spazio libero!) – Yakk

2

Se è davvero necessario usare bind, è possibile definire i propri tipi di segnaposto specializzandosi 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> { }; 
} 

Il motivo di questo è utile è che permette di mappare un intero ad un segnaposto in fase di compilazione tempo, che è possibile utilizzare con il integer_sequence trucco:

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 e gli amici sono C++ 14, ma possono essere implementati facilmente in C++ 11.

0

Non è necessario utilizzare std::bind ricorsivo per chiamare una funzione con una tupla di parametri quali valori possono essere valutati utilizzando indice parametro:

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

Esempio di utilizzo:

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

Come @T.C. indicato in his answerstd::make_index_sequence è una funzionalità di C++ 14 ma it can be implemented in C++11.