Esta es una forma de tener currying en C++ y puede o no ser relevante después de las ediciones recientes en el OP.
Debido a la sobrecarga es muy problemático inspeccionar un functor y detectar su aridad. Sin embargo, lo que es posible es que, dado un functor f
y un argumento a
, podemos verificar si f(a)
es una expresión válida. Si no es así, podemos almacenar a
y dado el siguiente argumento b
podemos verificar si f(a, b)
es una expresión válida, y así sucesivamente. A saber:
#include <utility>
#include <tuple>
/* Two SFINAE utilities */
template<typename>
struct void_ { using type = void; };
template<typename T>
using Void = typename void_<T>::type;
// std::result_of doesn't play well with SFINAE so we deliberately avoid it
// and roll our own
// For the sake of simplicity this result_of does not compute the same type
// as std::result_of (e.g. pointer to members)
template<typename Sig, typename Sfinae = void>
struct result_of {};
template<typename Functor, typename... Args>
struct result_of<
Functor(Args...)
, Void<decltype(std::declval<Functor>()(std::declval<Args>()...))>
> {
using type = decltype(std::declval<Functor>()(std::declval<Args>()...));
};
template<typename Functor, typename... Args>
using ResultOf = typename result_of<Sig>::type;
template<typename Functor, typename... Args>
class curry_type {
using tuple_type = std::tuple<Args...>;
public:
curry_type(Functor functor, tuple_type args)
: functor(std::forward<Functor>(functor))
, args(std::move(args))
{}
// Same policy as the wrappers from std::bind & others:
// the functor inherits the cv-qualifiers from the wrapper
// you might want to improve on that and inherit ref-qualifiers, too
template<typename Arg>
ResultOf<Functor&(Args..., Arg)>
operator()(Arg&& arg)
{
return invoke(functor, std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg))));
}
// Implementation omitted for brevity -- same as above in any case
template<typename Arg>
ResultOf<Functor const&(Args..., Arg)>
operator()(Arg&& arg) const;
// Additional cv-qualified overloads omitted for brevity
// Fallback: keep calm and curry on
// the last ellipsis (...) means that this is a C-style vararg function
// this is a trick to make this overload (and others like it) least
// preferred when it comes to overload resolution
// the Rest pack is here to make for better diagnostics if a user erroenously
// attempts e.g. curry(f)(2, 3) instead of perhaps curry(f)(2)(3)
// note that it is possible to provide the same functionality without this hack
// (which I have no idea is actually permitted, all things considered)
// but requires further facilities (e.g. an is_callable trait)
template<typename Arg, typename... Rest>
curry_type<Functor, Args..., Arg>
operator()(Arg&& arg, Rest const&..., ...)
{
static_assert(sizeof...(Rest) == 0
, "Wrong usage: only pass up to one argument to a curried functor");
return { std::forward<Functor>(functor), std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg))) };
}
// Again, additional overloads omitted
// This is actually not part of the currying functionality
// but is here so that curry(f)() is equivalent of f() iff
// f has a nullary overload
template<typename F = Functor>
ResultOf<F&(Args...)>
operator()()
{
// This check if for sanity -- if I got it right no user can trigger it
// It *is* possible to emit a nice warning if a user attempts
// e.g. curry(f)(4)() but requires further overloads and SFINAE --
// left as an exercise to the reader
static_assert(sizeof...(Args) == 0, "How did you do that?");
return invoke(functor, std::move(args));
}
// Additional cv-qualified overloads for the nullary case omitted for brevity
private:
Functor functor;
mutable tuple_type args;
template<typename F, typename Tuple, int... Indices>
ResultOf<F(typename std::tuple_element<Indices, Tuple>::type...)>
static invoke(F&& f, Tuple&& tuple, indices<Indices...>)
{
using std::get;
return std::forward<F>(f)(get<Indices>(std::forward<Tuple>(tuple))...);
}
template<typename F, typename Tuple>
static auto invoke(F&& f, Tuple&& tuple)
-> decltype(invoke(std::declval<F>(), std::declval<Tuple>(), indices_for<Tuple>()))
{
return invoke(std::forward<F>(f), std::forward<Tuple>(tuple), indices_for<Tuple>());
}
};
template<typename Functor>
curry_type<Functor> curry(Functor&& functor)
{ return { std::forward<Functor>(functor), {} }; }
El código anterior compila utilizando una instantánea de GCC 4.8 (salvo errores de copia y pegar), siempre que hay un tipo indices
y una utilidad de indices_for
. This question y su respuesta demuestra la necesidad y la implementación de tales cosas, donde seq
desempeña el papel de indices
y gens
se puede utilizar para implementar un (más conveniente) indices_for
.
Se tiene mucho cuidado en lo anterior cuando se trata de la categoría de valor y la duración de (posibles) temporales. curry
(y el tipo que lo acompaña, que es un detalle de implementación) está diseñado para ser lo más liviano posible a la vez que lo hace muy, muy seguro de usar. En particular, el uso de tales como:
foo a;
bar b;
auto f = [](foo a, bar b, baz c, int) { return quux(a, b, c); };
auto curried = curry(f);
auto pass = curried(a);
auto some = pass(b);
auto parameters = some(baz {});
auto result = parameters(0);
no copia f
, a
o b
; ni resulta en referencias pendientes a los temporales. Todo esto sigue siendo cierto incluso si auto
se sustituye por auto&&
(suponiendo que quux
está en su sano juicio, pero eso está más allá del control de curry
). Todavía es posible generar diferentes políticas al respecto (por ejemplo, decadencia sistemática).
Tenga en cuenta que los parámetros (pero no el funtor) se pasan con la misma categoría de valor en la llamada final que cuando se pasan a la envoltura curry. De ahí que en
auto functor = curry([](foo f, int) {});
auto curried = functor(foo {});
auto r0 = curried(0);
auto r1 = curried(1);
esto significa que se pasa a la funtor subyacente al calcular r1
un trasladó-de foo
.
Usted dice no boost, pero ¿por qué? Si no desea utilizar la biblioteca, puede copiar la funcionalidad que proporciona. – mydogisbox
Tu función de curry tiene la funcionalidad de bind. Alternativamente, puede usar 'auto fn = std :: bind ([] (int x, int y) {return x * y;}, std :: placeholders :: _ 1, 5);' – mkaes
mydogisbox: Porque en los cinco años He estado codificando, he empezado a despreciar a Boost. Podría volver a implementar alguna característica, si fuera lo suficientemente pequeña, pero no espero pequeña y funcional de Boost. Además, muchas de sus características se han convertido en estándar. Sin embargo, siempre estoy abierto a que se demuestre que estoy equivocado. – SplinterOfChaos