Dies ist eine Möglichkeit, in C++ zu curryen und kann nach den letzten Änderungen am OP relevant sein oder auch nicht.
Aufgrund der Überlastung ist es sehr problematisch, einen Funktor zu inspizieren und zu erkennen. Was jedoch möglich ist, ist, dass wir bei einem Funktor f
und einem Argument a
prüfen können, ob f(a)
ein gültiger Ausdruck ist. Wenn dies nicht der Fall ist, können wir a
speichern und mit folgendem Argument b
überprüfen, ob f(a, b)
ein gültiger Ausdruck ist und so weiter. Nämlich:
#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), {} }; }
Der obige Code kompiliert mit einem Schnappschuss von GCC 4.8 (abgesehen von Copy-and-Paste-Fehlern), vorausgesetzt, dass es ein indices
Typen und ein indices_for
Dienstprogramm. This question und seine Antwort zeigt die Notwendigkeit und Umsetzung solcher Dinge, wo seq
spielt die Rolle von indices
und kann verwendet werden, um eine (bequemere) indices_for
zu implementieren.
In Bezug auf Wertkategorie und Lebensdauer von (möglichen) Provisorien wird sehr sorgfältig darauf geachtet. curry
(und sein begleitender Typ, der ein Implementierungsdetail ist) ist so entworfen, um so leicht wie möglich zu sein, während es immer noch sehr, sehr sicher zu verwenden ist. Insbesondere Nutzung wie:
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);
kopiert nicht f
, a
oder b
; noch führt es dazu, dass Referenzen auf Provisorien erzeugt werden. Dies gilt immer noch, selbst wenn durch auto&&
ersetzt wird (vorausgesetzt, quux
ist gesund, aber das ist außerhalb der Kontrolle von curry
). Es ist immer noch möglich, diesbezüglich verschiedene Richtlinien zu entwickeln (z. B. systematisch zu verfallen).
Beachten Sie, dass Parameter (aber nicht der Funktor) mit der gleichen Wertkategorie im letzten Aufruf übergeben werden, als wenn sie an den Curry-Wrapper übergeben werden. Daher wird in
auto functor = curry([](foo f, int) {});
auto curried = functor(foo {});
auto r0 = curried(0);
auto r1 = curried(1);
Dies bedeutet, dass eine eingefahrene von foo
an dem darunterliegenden Funktors geben wird, wenn r1
berechnen.
Sie sagen, kein Boost, aber warum? Wenn Sie die Bibliothek nicht verwenden möchten, können Sie die bereitgestellten Funktionen kopieren. – mydogisbox
Ihre Curry-Funktion hat die Funktionalität von bind. Du könntest alternativ 'auto fn = std :: bind ([] (int x, int y) {return x * y;}, std :: platzhalter :: _ 1, 5);' – mkaes
mydogisbox: Weil in den fünf Jahren benutzen Ich habe programmiert, ich bin eine Verachtung für Boost geworden. Ich könnte ein Feature neu implementieren, wenn es ausreichend klein ist, aber ich erwarte von Boost nicht klein und funktional. Plus, viele seiner Funktionen sind Standard geworden. Ich bin jedoch immer offen dafür, sich als falsch zu erweisen. – SplinterOfChaos