2015-09-13 19 views
5

Sto provando a creare una funzione modello a cui è possibile passare qualche altra funzione con qualsiasi tipo e numero di parametri e associarla a un std::function. Sono riuscito a fare questo:Modelli variabili, tipo deduzione e std :: funzione

#include <iostream> 
#include <functional> 

int foo(int bar) 
{ 
    std::cout << bar << std::endl; 
    return bar; 
} 

template <typename Ret, typename... Args> 
std::function<Ret (Args...)> func(std::function<Ret (Args...)> f) 
{ 
    return f; 
} 

int main() 
{ 
    //auto barp = func(foo); // compilation error 
    auto bar = func(std::function<void (int)>(foo)); 

    bar (0); // prints 0 
} 

Vorrei chiamare solo auto barp = func(foo); e hanno i tipi dedurre, ma questa linea dà i seguenti errori di compilazione:

error: no matching function for call to ‘func(void (&)(int))’ 
    auto barp = func(foo); 
         ^
note: candidate is: 
note: template<class Ret, class ... Args> std::function<_Res(_ArgTypes ...)> func(std::function<_Res(_ArgTypes ...)>) 
std::function<Ret (Args...)> func(std::function<Ret (Args...)> f) 
          ^
note: template argument deduction/substitution failed: 
note: mismatched types ‘std::function<_Res(_ArgTypes ...)>’ and ‘int (*)(int)’ 
    auto barp = func(foo); 
        ^

Perché è cercando di abbinare std::function<_Res(_ArgTypes ...)> con int (*)(int)? Sento che dovrei ottenere in qualche modo il compilatore per espandere _Res(_ArgTypes ...) a int(int), ma come?

+4

Perché la conversione in un 'std :: function' a tutti? 'std :: function' è una classe di cancellazione dei tipi: prende le informazioni sul tipo di ciò da cui è costruita e ne cancella la maggior parte. La deduzione di tipo prende il suo argomento, ne deduce il tipo e genera codice. Stai chiedendo di dedurre di che tipo cancellare qualcosa. È come creare un'armatura che crea pistole per sparare a chi la indossa; il tipo di cancellazione è l'opposto * della deduzione del tipo nella maggior parte dei sensi. Raramente è una buona idea. È possibile, ma il C++ ha pochi motivi per renderlo * facile * Hai un caso pratico? – Yakk

+0

E i sovraccarichi o gli elenchi di argomenti variabili? Solo in alcune circostanze è possibile. Puoi comunque usare cose come boost function_traits. – tahsmith

+0

@Yakk, stavo cercando di codificare una funzione di "memoization", nel senso che restituirebbe un callable con gli stessi parametri e restituirà il tipo del suo argomento che chiamerebbe la funzione originale eccetto che cercherebbe i valori calcolati precedenti (da conservare in una mappa adatta). Vorrei usare 'std :: function' per memorizzare la funzione originale. Non sono sicuro che sia possibile. – Tarc

risposta

2

Una funzione non è std::function, è convertibile in uno. È possibile dedurre gli argomenti di una funzione, tuttavia, escludendo l'ambiguità relativa agli overload.

#include <iostream> 
#include <functional> 

int foo(int bar) 
{ 
    std::cout << bar << std::endl; 
    return 0; 
} 

// Will cause error. 
//int foo(double); 

template <typename Ret, typename... Args> 
std::function<Ret (Args...)> func(Ret f(Args...)) 
{ 
    return f; 
} 

int main() 
{ 
    auto bar = func(foo); 
    bar (0); // prints 0 
} 

Che cosa si vuole fare con l'originale std::function è simile a questa, che più evidentemente non funziona:

template<typename T> 
struct A 
{ 
    A(T); 
}; 

template<typename T> 
void func(A<T> a); 

int main() 
{ 
    func(42); 
} 

42 non è un A, può essere convertito in uno, però. Tuttavia, convertirlo in uno richiederebbe T per essere già noto.

+0

'& foo' è un po 'estraneo. Un nome di funzioni al di fuori di un'espressione chiamante viene convertito implicitamente in un puntatore di funzione in C/C++. – CoffeeandCode

+0

@CoffeeandCode, sì è cambiato. Preferisco averlo in generale, dato che è necessario per le funzioni dei membri. Ma qui è solo una complessità in più. – tahsmith

1

Il codice è semanticamente equivalente a (se compilato) questo:

int foo(int x){ 
    std::cout << x << std::endl; 
    return x; 
} 

int main(){ 
    auto bar = [](int x){ return foo(x); }; 
    bar(0); 
} 

Oltre alla parte return x, ma questo è solo me correggere il vostro comportamento non definito.

La funzione per la restituzione di std::function è estremamente inutile, tranne forse per una minore digitazione.

Si potrebbe facilmente utilizzare il costruttore std::function s senza la funzione wrapper.

Ma, ancora.

Per fare ciò che si vuole fare, si dovrebbe passare il puntatore della funzione stessa; senza conversione impossibile.

provare questo:

int foo(int x){ 
    return x + 1; 
} 

template<typename Ret, typename ... Args> 
auto func(Ret(*fp)(Args...)) -> std::function<Ret(Args...)>{ 
    return {fp}; 
} 

int main(){ 
    auto bar = func(foo); 
    std::cout << bar(0) << std::endl; // outputs '1' 
} 

Il motivo per il codice non funziona è a causa della conversione implicita cercando di prendere posto quando si passa un argomento per func.

Come ho già detto, il codice attualmente è semanticamente equivalente all'esempio che ho mostrato sopra usando espressioni lambda. Consiglio vivamente di usare solo espressioni lambda ovunque sia necessario il wrapping delle funzioni! Sono molto più flessibili e sono una parte fondamentale del linguaggio piuttosto che una funzionalità di libreria.

Ricordare che i lambda non catturanti sono convertibili in puntatori di funzioni; in modo che il seguente è conforme:

int main(){ 
    int(*bar)(int) = [](int x){ return x + 1; }; 
    std::cout << bar(0) << std::endl; 
} 

e di avere funzionalità simili a quelle che si desidera nel tuo post, potremmo scrivere qualcosa del genere:

int main(){ 
    auto func = +[](int x){ return x + 1; }; 

    std::cout << "foo(5) = " << func(5) << std::endl; 

    func = [](int x){ return x * 2; }; 

    std::cout << "bar(5) = " << func(5) << std::endl; 
} 

Avviso che non si scherza con le dichiarazioni puntatore a funzione o tipi di libreria? Molto più bello per tutti da leggere/scrivere. One thing to notice in this example is the unary + operator; to perform a conversion to function pointer before assigning it to the variable. Sembra davvero molto funzionale, che sembra essere quello che stai cercando di ottenere qui.

1

Prova a scartare il funtore (lambda e std :: funzione) attraverso la sua operator():

#include <iostream> 
#include <functional> 

int foo(int bar) 
{ 
    std::cout << bar << std::endl; 
    return 0; 
} 

template<typename /*Fn*/> 
struct function_maker; 

template<typename RTy, typename... ATy> 
struct function_maker<RTy(ATy...)> 
{ 
    template<typename T> 
    static std::function<RTy(ATy...)> make_function(T&& fn) 
    { 
     return std::function<RTy(ATy...)>(std::forward<T>(fn)); 
    } 
}; 

template<typename /*Fn*/> 
struct unwrap; 

template<typename CTy, typename RTy, typename... ATy> 
struct unwrap<RTy(CTy::*)(ATy...) const> 
    : function_maker<RTy(ATy...)> { }; 

template<typename CTy, typename RTy, typename... ATy> 
struct unwrap<RTy(CTy::*)(ATy...)> 
    : function_maker<RTy(ATy...)> { }; 

template<typename T> 
auto func(T f) 
    -> decltype(unwrap<decltype(&T::operator())>::make_function(std::declval<T>())) 
{ 
    return unwrap<decltype(&T::operator())>::make_function(std::forward<T>(f)); 
} 

int main() 
{ 
    //auto barp = func(foo); // compilation error 
    auto bar = func(std::function<void(int)>(foo)); 

    auto bar2 = func([](int) 
    { 
     // ... 
    }); 

    bar(0); // prints 0 
} 

Demo