wake-up-neo.com

Wie konvertiert man ein Lambda mithilfe von Vorlagen in eine std :: -Funktion

Grundsätzlich möchte ich in der Lage sein, ein Lambda mit einer beliebigen Anzahl von Parametern zu nehmen und es in eine std :: -Funktion zu konvertieren. Ich habe folgendes versucht und keine der beiden Methoden funktioniert.

std::function([](){});//Complains that std::function is missing template parameters
template <typename T> void foo(function<T> f){}
foo([](){});//Complains that it cannot find a matching candidate

Der folgende Code funktioniert zwar, aber ich möchte dies nicht, da er explizit die Parameter der Vorlage angibt, die nicht für generischen Code geeignet sind.

std::function<void()>([](){});

Ich habe den ganzen Abend mit Funktionen und Vorlagen herumgespielt und kann das einfach nicht verstehen, daher wäre jede Hilfe sehr dankbar.

Wie in einem Kommentar erwähnt, ist der Grund, warum ich versuche, dies zu tun, weil ich versuche, Currying in C++ unter Verwendung variadischer Vorlagen zu implementieren. Leider scheitert dies schrecklich bei der Verwendung von Lambdas. Zum Beispiel kann ich eine Standardfunktion mit einem Funktionszeiger übergeben.

template <typename R, typename...A>
void foo(R (*f)(A...)) {}
void bar() {}
int main() {
    foo(bar);
}

Ich kann jedoch nicht herausfinden, wie ein Lambda an eine solche variadische Funktion übergeben wird. Ich bin daran interessiert, ein generisches Lambda in eine std :: -Funktion zu konvertieren, weil ich Folgendes tun kann, aber am Ende muss ich die Template-Parameter explizit in std :: function angeben.

template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
    foo(std::function<void()>([](){}));
}
56
retep998

Sie können ein Lambda-Funktionsobjekt nicht als Argument des Typs std::function<T> übergeben, ohne das Vorlagenargument T explizit anzugeben. Der Vorlagentyp-Abzug versucht, den Typ Ihrer Lambda-Funktion an den std::function<T> anzupassen, was in diesem Fall einfach nicht möglich ist - diese Typen sind nicht gleich. Der Abzug von Vorlagentypen berücksichtigt Konvertierungen zwischen Typen nicht.

Es ist möglich, den Typ auf andere Weise abzuleiten. Sie können dies tun, indem Sie das Funktionsargument in einen identity-Typ einschließen, so dass es nicht fehlschlägt, wenn Sie versuchen, das Lambda mit std::function abzustimmen (da abhängige Typen nur durch Typabzug ignoriert werden) und andere Argumente angegeben werden.

template <typename T>
struct identity
{
  typedef T type;
};

template <typename... T>
void func(typename identity<std::function<void(T...)>>::type f, T... values) {
  f(values...);
}

int main() {
  func([](int x, int y, int z) { std::cout << (x*y*z) << std::endl; }, 3, 6, 8);
  return 0;
}

Dies ist in Ihrer Situation jedoch offensichtlich nicht hilfreich, da Sie die Werte erst später übergeben möchten.

Da Sie weder die Template-Parameter angeben möchten, noch andere Argumente übergeben möchten, aus denen die Template-Parameter abgeleitet werden können, kann der Compiler den Typ Ihres std::function-Arguments nicht ableiten.

42

Sie können einen dedizierten/retrospektiven Cast verwenden. Sobald Sie ein Werkzeug wie dieses haben

#include <functional>

using namespace std;

template<typename T>
struct memfun_type
{
    using type = void;
};

template<typename Ret, typename Class, typename... Args>
struct memfun_type<Ret(Class::*)(Args...) const>
{
    using type = std::function<Ret(Args...)>;
};

template<typename F>
typename memfun_type<decltype(&F::operator())>::type
FFL(F const &func)
{ // Function from lambda !
    return func;
}

sie können FFL() zu allen Lambda-Typen sagen, sie in die korrekte Version von std::function konvertieren

template <typename... Args> void Callback(std::function<void(Args...)> f){
    // store f and call later
}

int main()
{
    Callback(FFL([](int a, float b){
        // do something
    }));

    return 0;
}

Anzeige

20

Wie unter beim Ableiten der Anrufsignatur eines Lambdas oder beliebig für "make_function" aufrufbaren willkürlich können Sie die Anrufsignatur eines Lambdas (oder eines beliebigen anderen Funktors mit einer einzelnen Aufrufsignatur) aus seiner (einzigen) operator() entnehmen:

template<typename T> struct remove_class { };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...)> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); };

template<typename T>
struct get_signature_impl { using type = typename remove_class<
    decltype(&std::remove_reference<T>::type::operator())>::type; };
template<typename R, typename... A>
struct get_signature_impl<R(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(&)(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(*)(A...)> { using type = R(A...); };
template<typename T> using get_signature = typename get_signature_impl<T>::type;

Dies ist jedoch ein ziemlich unflexibler Ansatz. Wie R. Martinho Fernandes sagt, funktioniert es nicht für Funkeln mit mehreren operator()s, oder für Funkeln mit templatedoperator() oder (C++ 14) polymorphen Lambdas. Deshalb verschiebt bind die Rückführung des Ergebnistyps bis zum eventuellen Aufrufversuch.

13
ecatmur

Es ist möglich, den benötigten std :: -Funktionstyp für Lambda unter Verwendung von Ableitung, decltype, variadischen Vorlagen und einigen Typenmerkmalen zu erhalten:

namespace ambient {

    template <typename Function>
    struct function_traits : public function_traits<decltype(&Function::operator())> {};

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const> {
        typedef ReturnType (*pointer)(Args...);
        typedef const std::function<ReturnType(Args...)> function;
    };

    template <typename Function>
    typename function_traits<Function>::function to_function (Function& lambda) {
        return static_cast<typename function_traits<Function>::function>(lambda);
    }

    template <class L>
    struct overload_lambda : L {
        overload_lambda(L l) : L(l) {}
        template <typename... T>
        void operator()(T&& ... values){
            // here you can access the target std::function with
            to_function(*(L*)this)(std::forward<T>(values)...);
        }
    };

    template <class L>
    overload_lambda<L> lambda(L l){
        return overload_lambda<L>(l);
    }

}

Ich verwende es in meinem Code so:

ambient::lambda([&](const vector<int>& val){ // some code here // })(a);

PS: In meinem realen Fall speichere ich dieses std :: function-Objekt und seine Argumente in einem generischen Kernel-Objekt, das ich später bei Bedarf über virtuelle Funktionen ausführen kann. 

 

7
Alex Kosenkov

Das könnte für Sie interessant sein: https://Gist.github.com/Manu343726/94769034179e2c846acc

Das ist ein Experiment, das ich vor einem Monat geschrieben habe. Das Ziel bestand darin, eine funktorartige C++ - Vorlage zu erstellen, die Haskells partielle Schließungen simuliert, d. H. Die automatische Erstellung einer Schließung von m-n-Argumenten, wenn Sie mit n-Argumenten eine Funktion mit m-Parametern aufrufen. 

Dies ist ein Beispiel dafür, was dieses Experiment tun kann:

int f( int a, int b, int c, int d)
{
    return a+b+c+d;
}

int main()
{
    auto foo = haskell::make_function( f );

    auto a = foo , 1 , 2 , 3; //a is a closure function object with one parameter

    std::cout << a , 4 << std::endl; //Prints 10
}

haskell::make_function verwendet einige Typmerkmale, um die verschiedenen Typen von Funktionsentitäten zu berücksichtigen, einschließlich Lambdas:

auto f = haskell::make_function( []( int x, int y , int z ){ return x*y*z; } );

auto a = f(1,2); //a is functor with one parameter (Using the alternative C++-like syntax)
auto b = a(3); // b is 6

Wie Sie sehen, verwende ich den Kommaoperator, um die Haskell-Syntax zu ändern, aber Sie könnten ihn zum Aufrufoperator ändern, um Ihre Zielsyntax zu erreichen.

Es steht Ihnen völlig frei, mit dem Code alles zu tun, was Sie möchten (Lizenz überprüfen).

3
Manu343726

Ist das Currying nicht bereits mit std::bind implementiert?

auto sum = [](int a, int b){ return a+b; };
auto inc = std::bind( sum, _1, 1 );
assert( inc(1)==2 );
3
xtofl

In C++ 17 gibt es den Konstruktortyp. Sie können also einige Eingaben für die Argumente der std :: function-Vorlage speichern. Das ist nicht ganz nichts, aber ein bisschen weniger.

template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
   foo(std::function([](){}));
}    
0
user2281723