c++ boost lambda boost-phoenix boost-proto

c++ - Funciones estáticas de boost.lambda o boost.phoenix



boost-phoenix boost-proto (1)

Regularmente uso boost.lambda (y fénix) para definir funciones lambda en C ++. Me gusta mucho su propiedad polimórfica, la simplicidad de su representación y la manera en que hacen que la programación funcional en C ++ sea mucho más fácil. En algunos casos, es incluso más limpio y más legible (si está acostumbrado a leerlos) para usarlos para definir funciones pequeñas y nombrarlas en el ámbito estático.

La forma de almacenar estos funcionales que se parece más a las funciones convencionales es capturarlos en una boost::function

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

Pero el problema es la ineficiencia en tiempo de ejecución al hacerlo. Aunque la función de add aquí es sin estado, el tipo de lambda devuelto no está vacío y su sizeof es mayor que 1 (por lo tanto boost::function default ctor y copy ctor implicarán new ). Realmente dudo que exista un mecanismo del lado del compilador o del refuerzo para detectar esta apatridia y generar código que sea equivalente a usar:

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

Uno podría, por supuesto, usar c ++ 11 auto , pero luego la variable no se puede pasar alrededor de contextos sin templado. Finalmente logré hacer casi lo que quiero, utilizando el siguiente enfoque:

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

Compilado con gcc 4.6.1 El resultado de este programa es (independientemente del nivel de optimización):

25 Segmentation fault

como se esperaba. Aquí, guardo un puntero estático para el tipo de expresión lambda (lo más posible para fines de optimización) e inicializándolo a NULL . De esta forma, si intenta "estatizar" una expresión lambda con estado, seguramente obtendrá un error de tiempo de ejecución. Y si se estatiza una expresión lambda genuinamente sin estado, todo funciona.

En la (s) pregunta (s):

  1. El método parece un poco sucio, ¿puedes pensar en alguna circunstancia o supuesto del compilador que haga que esto se comporte mal? (Comportamiento esperado: funciona bien si lambda no tiene estado, segfault de lo contrario).

  2. ¿Puedes pensar en alguna forma de intentar esto ocasionando un error de compilación en lugar de un segfault cuando la expresión lambda tiene estado?

EDIT después de la respuesta de 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. No hay forma de hacer esto más limpio. Está llamando a una función miembro a través de un puntero nulo. Esto es todo tipo de comportamiento indefinido, pero ya lo sabes.
  2. No puede saber si una función Boost.Lambda es sin estado. Es una caja negra. Boost.Phoenix es una historia diferente. Se basa en Boost.Proto, un kit de herramientas DSL, y Phoenix publica su gramática y le da ganchos para introspectar las expresiones lambda que genera. Puede escribir fácilmente un algoritmo Proto para buscar terminales con estado y bombardear en tiempo de compilación si encuentra alguno. (Pero eso no cambia mi respuesta al n. ° 1 anterior).

Dijiste que te gustaba la naturaleza polimórfica de las funciones lambda de Boost, pero no estás usando esa propiedad en tu código anterior. Mi sugerencia: usar C ++ 11 lambdas. Los sin estado ya tienen una conversión implícita a punteros de función sin formato. Es justo lo que estás buscando, OMI.

=== ACTUALIZACIÓN ===

Aunque llamar a una función miembro a través de un puntero nulo es una idea terrible (no lo hagas, te quedarás ciego), puedes construir por defecto un NUEVO objeto lambda del mismo tipo del original. Si combinas eso con mi sugerencia en el n. ° 2 anterior, puedes obtener lo que buscas. Aquí está el código:

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

Descargo de responsabilidad: no soy el autor de Phoenix. No sé si se garantiza la construcibilidad predeterminada para todas las lambdas sin estado.

Probado con MSVC-10.0.

¡Disfrutar!