2012-04-13 12 views
8

Uso regolarmente boost.lambda (e phoenix) per definire le funzioni lambda in C++. Mi piace molto la loro proprietà polimorfica, la semplicità della loro rappresentazione e il modo in cui rendono la programmazione funzionale in C++ molto più facile. In alcuni casi, è anche più pulito e più leggibile (se si è abituati a leggerli) per usarli per definire piccole funzioni e nominarle nell'ambito statico.Funzioni statiche da boost.lambda o boost.phoenix

Il modo per memorizzare questi funzionali che ricorda le funzioni convenzionali di più è di catturarli in un boost::function

const boost::function<double(double,double)> add = _1+_2; 

Ma il problema è l'inefficienza runtime di farlo. Anche se la funzione add è stateless, il tipo lambda restituito non è vuoto e il suo sizeof è maggiore di 1 (quindi il valore predefinito di boost::function ctor e copy ctor è new). Realmente dubito che c'è un meccanismo dal compilatore o lato del spinta per rilevare questo apolidia e generare codice che è equivalente all'utilizzo:

double (* const add)(double,double) = _1+_2; //not valid right now 

Si potrebbe naturalmente utilizzare il C++ 11 auto, ma poi la variabile non può essere passato intorno a contesti non basati su modelli. Sono finalmente riuscito a fare quasi quello che voglio, utilizzando il seguente approccio:

#include <boost/lambda/lambda.hpp> 
using namespace boost::lambda; 

#include <boost/type_traits.hpp> 
#include <boost/utility/result_of.hpp> 
using namespace boost; 


template <class T> 
struct static_lambda { 

    static const T* const t; 

    // Define a static function that calls the functional t 
    template <class arg1type, class arg2type> 
    static typename result_of<T(arg1type,arg2type)>::type 
     apply(arg1type arg1,arg2type arg2){ 
     return (*t)(arg1,arg2); 
    } 

    // The conversion operator 
    template<class func_type> 
    operator func_type*() { 
     typedef typename function_traits<func_type>::arg1_type arg1type; 
     typedef typename function_traits<func_type>::arg2_type arg2type; 
     return &static_lambda<T>::apply<arg1type,arg2type>; 
    } 
}; 

template <class T> 
const T* const static_lambda<T>::t = 0; 

template <class T> 
static_lambda<T> make_static(T t) {return static_lambda<T>();} 

#include <iostream> 
#include <cstdio> 


int main() { 
    int c=5; 
    int (*add) (int,int) = make_static(_1+_2); 
    // We can even define arrays with the following syntax 
    double (*const func_array[])(double,double) = {make_static(_1+_2),make_static(_1*_2*ref(c))}; 
    std::cout<<func_array[0](10,15)<<"\n"; 
    std::fflush(stdout); 
    std::cout<<func_array[1](10,15); // should cause segmentation fault since func_array[1] has state 
} 

compilato con GCC 4.6.1 L'output di questo programma è (a prescindere dal livello di ottimizzazione):

25 
Segmentation fault 

come previsto. Qui, sto mantenendo un puntatore statico al tipo di espressione lambda (come const possibile per scopi di ottimizzazione) e inizializzandolo su NULL. In questo modo, se provi a "staticificare" un'espressione lambda con stato, sei sicuro di ottenere un errore di runtime. E se si statica un'espressione lambda autenticamente senza stato, tutto funziona.

alla questione (s):

  1. Il metodo sembra un po 'sporco, si può pensare a qualsiasi circostanza, o compilatore presupposto che renderà questo misbehave (comportamento previsto: funzionano bene se lambda è senza stato, segfault altrimenti).

  2. Riesci a pensare a un modo in cui il tentativo di questo causerà un errore del compilatore invece di un segfault quando l'espressione lambda ha uno stato?

EDIT dopo la risposta di Eric Niebler:

#include <boost/phoenix.hpp> 
using namespace boost::phoenix; 
using namespace boost::phoenix::arg_names; 

#include <boost/type_traits.hpp> 
#include <boost/utility/result_of.hpp> 
using boost::function_traits; 

template <class T> 
struct static_lambda { 
    static const T t; 

    // A static function that simply applies t 
    template <class arg1type, class arg2type> 
    static typename boost::result_of<T(arg1type,arg2type)>::type 
    apply(arg1type arg1,arg2type arg2){ 
    return t(arg1,arg2); 
    } 

    // Conversion to a function pointer 
    template<class func_type> 
    operator func_type*() { 
    typedef typename function_traits<func_type>::arg1_type arg1type; 
     typedef typename function_traits<func_type>::arg2_type arg2type; 
     return &static_lambda<T>::apply<arg1type,arg2type>; 
    } 
}; 

template <class T> 
const T static_lambda<T>::t; // Default initialize the functional 

template <class T> 
static_lambda<T> make_static(T t) {return static_lambda<T>();} 

#include <iostream> 
#include <cstdio> 


int main() { 
    int (*add) (int,int) = make_static(_1+_2); 

    std::cout<<add(10,15)<<"\n"; 

    int c=5; 

    // int (*add_with_ref) (int,int) = make_static(_1+_2+ref(c)); causes compiler error as desired 
} 
+1

IIRC, se si utilizza Phoenix, è possibile memorizzare il risultato all'interno di una funzione 'boost :: phoenix ::' piuttosto che una funzione' boost :: 'e attenuare una perdita di efficienza ('boost :: phoenix: : function' sono tipi POD e possono essere inizializzati staticamente in fase di compilazione). – ildjarn

+0

@ildjarn Grazie per l'idea di 'boost :: phoenix :: function', che in molti casi sarà utile. Sono comunque interessato a ottenere la funzione lambda equivalente a quella nativa (runtime-performance wise). Non sono sicuro che sia possibile rendere questa qualità di produzione, ma trovo interessante la ricerca. – enobayram

risposta

11
  1. Non v'è alcun modo per rendere questo pulitore. Stai chiamando una funzione membro tramite un puntatore nullo. Questo è ogni tipo di comportamento indefinito, ma lo sai già.
  2. Non è possibile sapere se una funzione Boost.Lambda è stateless. È una scatola nera. Boost.Phoenix è una storia diversa. È basato su Boost.Proto, un toolkit DSL, e Phoenix pubblica la sua grammatica e ti dà i ganci per analizzare le espressioni lambda che genera. Puoi facilmente scrivere un algoritmo di Proto per cercare terminali di stato e bombardare al momento della compilazione, se ne trova uno. (Ma ciò non cambia la mia risposta al punto 1 sopra.

Hai detto che ti piace la natura polimorfica delle funzioni lambda di Boost, ma non stai usando quella proprietà nel tuo codice qui sopra. Il mio consiglio: usa C++ 11 lambda. Gli apolidi hanno già una conversione implicita ai puntatori di funzioni raw. È proprio quello che stai cercando, IMO.

=== === UPDATE

Anche se chiamare una funzione di membro attraverso un puntatore nullo è una pessima idea (non farlo, andrai cieco), si può Predefinito- costruire un NEW oggetto lambda dello stesso tipo dell'originale. Se lo combini con il mio suggerimento in # 2 sopra, puoi ottenere ciò che cerchi. Ecco il codice:

#include <iostream> 
#include <type_traits> 
#include <boost/mpl/bool.hpp> 
#include <boost/mpl/and.hpp> 
#include <boost/phoenix.hpp> 

namespace detail 
{ 
    using namespace boost::proto; 
    namespace mpl = boost::mpl; 

    struct is_stateless 
     : or_< 
      when<terminal<_>, std::is_empty<_value>()>, 
      otherwise< 
       fold<_, mpl::true_(), mpl::and_<_state, is_stateless>()> 
      > 
     > 
    {}; 

    template<typename Lambda> 
    struct static_lambda 
    { 
     template<typename Sig> 
     struct impl; 

     template<typename Ret, typename Arg0, typename Arg1> 
     struct impl<Ret(Arg0, Arg1)> 
     { 
      static Ret apply(Arg0 arg0, Arg1 arg1) 
      { 
       return Lambda()(arg0, arg1); 
      } 
     }; 

     template<typename Fun> 
     operator Fun*() const 
     { 
      return &impl<Fun>::apply; 
     } 
    }; 

    template<typename Lambda> 
    inline static_lambda<Lambda> make_static(Lambda const &l) 
    { 
     static_assert(
      boost::result_of<is_stateless(Lambda)>::type::value, 
      "Lambda is not stateless" 
     ); 
     return static_lambda<Lambda>(); 
    } 
} 

using detail::make_static; 

int main() 
{ 
    using namespace boost::phoenix; 
    using namespace placeholders; 

    int c=5; 
    int (*add)(int,int) = make_static(_1+_2); 

    // We can even define arrays with the following syntax 
    static double (*const func_array[])(double,double) = 
    { 
     make_static(_1+_2), 
     make_static(_1*_2) 
    }; 
    std::cout << func_array[0](10,15) << "\n"; 
    std::cout << func_array[1](10,15); 

    // If you try to create a stateless lambda from a lambda 
    // with state, you trigger a static assertion: 
    int (*oops)(int,int) = make_static(_1+_2+42); // ERROR, not stateless 
} 

Disclaimer: Io non sono l'autore di Phoenix. Non so se sia garantita la costruttività di default per tutti i lambda senza stato.

Testato con MSVC-10.0.

Divertiti!

+2

+1, non c'è niente come una risposta autorevole. : -] – ildjarn

+0

Ho appena visto il tuo aggiornamento, ho anche provato a usare Phoenix lambda dopo aver letto la tua risposta originale e ho notato che supportano la costruzione predefinita su lambda senza stato. Ho anche trovato una soluzione che usa quella proprietà (controlla la mia modifica). Anche se penso che la tua soluzione sia migliore e più didattica :) – enobayram

+2

Attenzione lì. Non sono solo i lambda stateless Phoenix che sono costruttibili in modo predefinito; la tua soluzione non catturerà lambda che catturano i locali per valore, a patto che i tipi di questi locali siano essi stessi costruttivi di default. Hai davvero bisogno di usare l'algoritmo di proto 'is_stateless' che ho scritto sopra. –

Problemi correlati