2010-05-15 14 views
9

Hay muchas bibliotecas impresionantes de Boost como Boost.Lambda o Boost.Phoenix que hacen que C++ se convierta en un lenguaje verdaderamente funcional. Pero, ¿existe una manera directa de crear una función compuesta a partir de 2 o más funciones o funtores arbitrarios?Composición de funciones en C++

Si tengo: int f(int x) y int g(int x), quiero hacer algo como f . g lo que generaría un nuevo objeto estáticamente función equivalente a f(g(x)).

Esto parece ser posible a través de diversas técnicas, tales como las discutidas here. Ciertamente, puede encadenar llamadas al boost::lambda::bind para crear un funtor compuesto. Pero, ¿hay algo en Boost que le permita tomar fácilmente 2 o más funciones u objetos funcionales y combinarlos para crear un solo funtor compuesto, similar a cómo lo haría en un lenguaje como Haskell?

+1

puede usar 'impulsar :: bind' en lugar de' boost :: lambda :: bind' para esta tarea. Se ve como 'bind (g, bind (f, _1))'. ¿Sabes de eso? –

+0

Sí, puede usar llamadas anidadas a 'boost :: bind' para crear funtores compuestos. Sin embargo, me preguntaba si había alguna forma mejor de hacerlo. – Channel72

Respuesta

3

No conozco nada que admita la sintaxis que desea actualmente. Sin embargo, sería una simple cuestión de crear uno. Simplemente anule * para funtores (boost :: function <> por ejemplo) para que devuelva un functor compuesto.


template < typename R1, typename R2, typename T1, typename T2 > 
boost::function<R1(T2)> operator * (boost::function<R1(T2)> const& f, boost::function<R2(T2)> const& g) 
{ 
    return boost::bind(f, boost::bind(g, _1)); 
} 

No comprobado, pero sospecho que está cerca si no funciona de la caja.

+1

Dado que las funciones en bruto no son ni clases ni enumeraciones, un uso como 'f1 * f2' no buscará operadores sobrecargados, aunque :(Utilizará el operador incorporado y fallará ya que no puede multiplicar dos funciones. –

+0

Esa es la cuestión de tener funciona como ciudadanos de segunda clase ... –

+0

Sí, solo funciona con funtores. Podría hacer una función de utilidad que arroje una función boost :: para que coincida con las funciones normales. –

1

Plantilla ellos.

template<typename T1> class FunctorOne { 
    FunctorOne(T1 newt) 
     : t(newt) {} 
    void operator()() { 
     t(); 
    } 
    T1 t; 
}; 
template<> class FunctorOne<void> { 
    void operator()() { 
    } 
}; 
template<typename T1> class FunctorTwo { 
    FunctorOne(T1 newt) 
     : t(newt) {} 
    void operator()() { 
     t(); 
    } 
    T1 t; 
}; 
template<> class FunctorTwo<void> { 
    void operator()() { 
    } 
}; 
FunctorOne<FunctorTwo<FunctorOne<FunctorTwo<void>>>>> strangefunctionobject(FunctorTwo(FunctorOne(FunctorTwo())); 

Se recomienda el uso excelente de typedefs.
Editar: ¡Vaya! Resulta que la inferencia de tipo en constructores apesta. Volveré en un minuto con algo que realmente funciona: P
Aún más edite:
Si quisiera simplemente funtores en lugar de funcionantes, podría simplemente crear una nueva instancia, o incluso simplemente usar funciones estáticas.

template<typename T1, typename T2> class FunctorOne { 
public: 
    static bool Call() { 
     T1::Call(T2::Call()); 
     return true; 
    } 
}; 
template<> class FunctorOne<void, void> { 
public: 
    static bool Call() { 
    } 
}; 
template<typename T1> class FunctorTwo { 
public: 
    static bool Call() { 
     T1::Call(); 
    } 
}; 
template<> class FunctorTwo<void> { 
public: 
    static bool Call() { 
    } 
}; 

bool haicakes = FunctorOne<FunctorTwo<void>, FunctorTwo<void>>::Call(); 

Esto supone que en cualquier función dada, puede manejar cada firma diferente de forma manual. El uso de decltype podría ayudar a este respecto con un compilador C++ 0x.

4

Tropezando con esta pregunta, me gustaría señalar a cualquiera que se encuentre con esto hoy en día que esto es posible con una sintaxis relativamente elegante utilizando solo la biblioteca estándar y algunas clases de ayuda gracias a decltype, auto y perfecto reenvío.

la definición de estas dos clases:

template <class Arg, class ArgCall, class OuterCall> 
class pipe { 
private: 
    ArgCall argcall; 
    OuterCall outercall; 
public: 
    typedef pipe<Arg, ArgCall, OuterCall> this_type; 
    pipe(ArgCall ac, OuterCall oc) : argcall(ac), outercall(oc) {} 
    auto operator()(Arg arg) -> decltype(outercall(argcall(arg))) { 
     return outercall(argcall(arg)); 
    } 
    template <class NewCall> 
    pipe<Arg, this_type, NewCall> operator[](NewCall&& nc) { 
     return {*this, std::forward<NewCall>(nc)}; 
    } 
}; 

template <class Arg> 
class pipe_source { 
public: 
    typedef pipe_source<Arg> this_type; 
    Arg operator()(Arg arg) { 
     return arg; 
    } 
    template <class ArgCall, class OuterCall> 
    static pipe<Arg, ArgCall, OuterCall> create(ArgCall&& ac, OuterCall&& oc) { 
     return {std::forward<ArgCall>(ac), std::forward<OuterCall>(oc)}; 
    } 
    template <class OuterCall> 
    pipe<Arg, this_type, OuterCall> operator[](OuterCall&& oc) { 
     return {*this, std::forward<OuterCall>(oc)}; 
    } 
}; 

Un programa sencillo:

int f(int x) { 
     return x*x; 
} 

int g(int x) { 
     return x-2; 
} 

int h(int x) { 
     return x/2; 
} 

int main() { 
     auto foo = pipe_source<int>::create(f, g); 
     //or: 
     auto bar = pipe_source<int>()[g][h]; 
     std::cout << foo(10) << std::endl; 
     std::cout << bar(10) << std::endl; 
     return 0; 
} 

Esto tiene la ventaja añadida de que una vez que está en una tubería, siempre que el tipo de retorno es correcta puede agregar otra función f a la cadena con tubería [f].

continuación:

$ g++ test.cpp -o test -std=c++11 
$ ./test 
98 
4 
$ 
0

C++ 11. Sin impulso Sin clases de ayuda. Cualquier cantidad de argumentos Just std :: function y plantillas variadic.

template <typename F1, typename F2> 
struct function_composition_traits : public function_composition_traits<decltype(&F1::operator()), decltype(&F2::operator())> 
{}; 

template <typename ClassType1, typename ReturnType1, typename... Args1, typename ClassType2, typename ReturnType2, typename... Args2> 
struct function_composition_traits<ReturnType1(ClassType1::*)(Args1...) const, ReturnType2(ClassType2::*)(Args2...) const> 
{ 
     typedef std::function<ReturnType2(Args1...)> composition; 

     template <typename Func1, typename Func2> 
     inline static composition compose(const Func1& f1, const Func2& f2) { 
      return [f1,f2](Args1... args) -> ReturnType2 { return f2(f1(std::forward<Args1>(args)...)); }; 
     } 
}; 

template <typename F1, typename F2> 
typename function_composition_traits<F1,F2>::composition compose(const F1& lambda1,const F2& lambda2) 
{ 
     return function_composition_traits<F1,F2>::template compose<F1,F2>(lambda1, lambda2); 
} 

template <typename F, typename... Fs> 
auto compose(F f, Fs... fs) -> decltype(compose(f, compose(fs...))) 
{ 
     return compose(f, compose(std::forward<Fs>(fs)...)); 
} 

Uso:

auto add = [](int x, int y){ return x+y; }; 
auto mul = [](int x){ return x*2; }; 
auto divide = [](int x) { return (double)x/3.0; }; 
auto test = compose(add, mul, divide); 

cout << "Result: " << test(2,3); 

Salida:

Resultado: 3.33333

Cuestiones relacionadas