web-dev-qa-db-ger.com

Kann der "Typ" eines Lambda-Ausdrucks ausgedrückt werden?

Wenn Sie sich Lambda-Ausdrücke als „syntaktischen Zucker“ für aufrufbare Objekte vorstellen, kann der unbenannte zugrunde liegende Typ ausgedrückt werden? 

Ein Beispiel: 

struct gt {
    bool operator() (int l, int r) {
        return l > r;
    }
} ;

Nun ist [](int l, int r) { return l > r; } ein eleganter Ersatz für den obigen Code (plus der erforderlichen Erstellung von aufrufbaren Objekten von gt), aber gibt es eine Möglichkeit, gt (den Typ) selbst auszudrücken? 

Eine einfache Verwendung: 

std::set<int, gt> s1;  // A reversed-order std::set
// Is there a way to do the same using a lambda?
std::set<int, some-magic-here-maybe([](int l, int r) { return l > r; }) > s2;
51

Nein, Sie können es nicht in decltype setzen, weil

Ein Lambda-Ausdruck darf nicht in einem nicht ausgewerteten Operanden erscheinen

Sie können jedoch Folgendes tun

auto n = [](int l, int r) { return l > r; };
std::set<int, decltype(n)> s(n);

Das ist aber wirklich hässlich. Beachten Sie, dass jeder Lambda-Ausdruck einen neuen eindeutigen Typ erstellt. Wenn Sie anschließend an anderer Stelle Folgendes tun, hat t einen anderen Typ als s

auto n = [](int l, int r) { return l > r; };
std::set<int, decltype(n)> t(n);

Sie können hier std::function verwenden. Beachten Sie jedoch, dass dies nur geringfügige Laufzeitkosten verursacht, da ein indirekter Aufruf des Lambda-Funktionsobjekt-Aufrufoperators erforderlich ist. Dies ist wahrscheinlich vernachlässigbar, kann jedoch von Bedeutung sein, wenn Sie Funktionsobjekte auf diese Weise beispielsweise an std::sort übergeben möchten.

std::set<int, function<bool(int, int)>> s([](int l, int r) { return l > r; });

Wie immer zuerst Code, dann Profil :)

Direkte Antwort auf Ihre Frage: Nein.

Sie müssen etwas verwenden, das von einem beliebigen Typ zugewiesen werden kann, der einem Funktionsbereich ähnelt, der über einen gut definierten Typ verfügt. Ein Beispiel ist std :: function, wie in der Antwort von sbi gezeigt. Das ist jedoch nicht der Typ des Lambda-Ausdrucks.

1
Crazy Eddie

Zumindest in Microsoft Visual Studio (ich habe es nicht mit anderen Compilern versucht), und wenn Sie nichts aufnehmen, scheint der Typ ein regulärer Funktionszeiger zu sein:

std::string (*myFunctionPointer)(int x) = [] (int x) {
  char buffer[10];
  return std::string("Test ") + itoa(x, buffer, 10);
};
std::string testOutput = myFunctionPointer(123);

Sie können eine kleine Klasse lambda_wrapper <> verwenden, um ein Lambda zu geringen Kosten einzuwickeln. Es ist viel schneller als std :: function, da es keinen virtuellen Funktionsaufruf und eine dynamische Speicherzuweisung gibt. Der Wrapper leitet die Liste der Lambda-Argumente und den Rückgabetyp ab.

#include <iostream>
#include <functional>
#include <set>

template <typename T, typename ... Args>
struct lambda_wrapper : public lambda_wrapper<decltype(&T::operator())(Args...)> {};

template <typename L>
struct lambda_wrapper<L> {
private:
    L lambda;

public:
    lambda_wrapper(const L & obj) : lambda(obj) {}

    template<typename... Args>
    typename std::result_of<L(Args...)>::type operator()(Args... a) {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }

    template<typename... Args> typename
    std::result_of<const L(Args...)>::type operator()(Args... a) const {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }
};
template <typename T>
auto make_lambda_wrapper(T&&t) {
    return lambda_wrapper<T>(std::forward<T>(t));
}
int main(int argc, char ** argv) 
{
    auto func = make_lambda_wrapper([](int y, int x) -> bool { return x>y; });
    std::set<int, decltype(func)> ss(func);
    std::cout << func(2, 4) << std::endl;
}
0
barney