Überladen Sie eine Lambda-Funktion

14

Wie kann man eine einfache lokale Lambda-Funktion überladen?

SSE des ursprünglichen Problems:

#include <iostream>
#include <map>

void read()
{
    static std::string line;
    std::getline(std::cin, line);

    auto translate = [](int idx)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    };

    auto translate = [](char c)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                             {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[c];
    };

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));
    std::cout << r << c << std::endl;
}

int main()
{
    read();
    return 0;
}

Die Fehlermeldungen

error: conflicting declaration 'auto translate'
note: previous declaration as 'read()::<lambda(int)> translate'

Bitte haben Sie nichts dagegen, Benutzereingaben nicht zu überprüfen, dies ist eine SSE.

snoopy
quelle
7
Lambdas sind keine Funktionen, sondern Objekte, sodass eine Überladung für sie niemals gilt. translatesind nur lokale Variablen, die denselben Namen nicht wiederverwenden können.
user7860670
2
Verwandte / Betrüger: stackoverflow.com/questions/32475576/…
NathanOliver

Antworten:

10

Nein, Sie können das Lambda nicht überladen!

Die Lambdas sind anonyme Funktoren (dh unbenannte Funktionsobjekte) und keine einfachen Funktionen. Daher ist eine Überladung dieser Objekte nicht möglich. Was Sie im Grunde versuchen, ist fast

struct <some_name>
{
    int operator()(int idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

struct <some_name>
{
    int operator()(char idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

Dies ist nicht möglich, da derselbe Variablenname in C ++ nicht wiederverwendet werden kann.


In wir jedochif constexpr die Möglichkeit, den einzigen Zweig zu instanziieren, der zur Kompilierungszeit wahr ist.

Die möglichen Lösungen sind:

  • Eine einzelne variable Vorlage Lambda. oder
  • Ein generisches Lambda und finden Sie den Typ des Parameters, der decltype für die if constexprPrüfung verwendet wird. (Credits @NathanOliver )

Mit variabe template können Sie so etwas tun. ( Sehen Sie eine Live-Demo online )

#include <type_traits> // std::is_same_v

template<typename T>
constexpr auto translate = [](T idx) 
{
    if constexpr (std::is_same_v<T, int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<T, char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

und nenne es wie

int r = translate<int>(line[0]);
int c = translate<char>(line[1]);

Bei Verwendung von generischem Lambda (seit ) gilt Folgendes: ( Sehen Sie sich eine Live-Demo online an. )

#include <type_traits> // std::is_same_v

constexpr auto translate = [](auto idx) 
{
    if constexpr (std::is_same_v<decltype(idx), int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<decltype(idx), char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

und nenne das Lambda wie jetzt:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
JeJo
quelle
3
Ich finde das erstaunlich
snoopy
1
Erstens müssen Sie else ifsein else if constexpr. Zweitens, warum eine variable Vorlage verwenden? Sie könnten einfach das Lambda generisch machen und Ihre Checls würden if constexpr (std::is_same_v<decltype(idx), int>)undelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver
6

Lambdas sind im Grunde syntaktischer Zucker für lokal definierte Funktoren. Soweit ich weiß, sollten sie niemals überladen werden, um mit verschiedenen Parametern aufgerufen zu werden. Beachten Sie, dass jeder Lambda-Ausdruck von einem anderen Typ ist, sodass Ihr Code trotz des unmittelbaren Fehlers nicht wie beabsichtigt funktionieren kann.

Sie können jedoch einen Funktor mit einer Überlastung definieren operator() . Dies ist genau das, was Sie von Lambdas bekommen würden, wenn es möglich wäre. Sie erhalten nur nicht die knappe Syntax.

Etwas wie:

void read()
{
    static std::string line;

    struct translator {
          int operator()(int idx) { /* ... */ }
          int operator()(char x)  { /* ... */ }
    };
    translator translate;


    std::getline(std::cin, line);

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));

    std::cout << r << c << std::endl;
}
idclev 463035818
quelle
Moment mal, du nennst Lambda-Syntax nett?
user7860670
1
@VTT es ist schön, dass die Syntax knapp ist. Im Vergleich zu einigen älteren Dingen ist es nicht schlecht
idclev 463035818
5

Daher gelten die Regeln zum Überladen von Namen nur für bestimmte Arten der Suche nach Funktionsnamen (sowohl freie als auch Methoden).

Lambdas sind keine Funktionen, sondern Objekte mit einem Funktionsaufrufoperator. Eine Überladung zwischen zwei verschiedenen Lambdas kann also nicht auftreten.

Jetzt können Sie die Überlastungsauflösung für die Arbeit mit Funktionsobjekten erhalten, jedoch nur im Rahmen eines einzelnen Objekts. Und wenn es mehr als eine gibt operator(), kann eine Überlastungsauflösung zwischen ihnen wählen.

Ein Lambda hat jedoch keine offensichtliche Möglichkeit, mehr als ein Lambda zu haben operator(). Wir können eine einfache (in ) Dienstprogrammklasse schreiben , um uns zu helfen:

template<class...Fs>
struct overloaded : Fs... {
  using Fs::operator()...;
};

und eine Abzugsanleitung:

template<class...Fs>
overloaded(Fs...) -> overloaded<Fs...>;

Mit diesen beiden können wir zwei Lambdas überladen:

static std::string line;
std::getline(std::cin, line);

auto translate_int = [](int idx){
    constexpr static int table[8] {7,6,5,4,3,2,1,0};
    return table[idx];
};

auto translate_char = [](char c) {
    std::map<char, int> table { {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
    return table[c];
};
auto translate = overloaded{ translate_int, translate_char };

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));

und fertig.

Das Schreiben overloadedist sowohl in als auch in , erfordert jedoch mehr Arbeit und ist weniger elegant. Sobald Sie sich des Problems bewusst sind, sollte es nicht schwierig sein, eine Lösung zu finden, die den Anforderungen Ihres Compilers in Bezug auf C ++ - Funktionen entspricht.

Yakk - Adam Nevraumont
quelle
Soweit ich weiß, hat jedes "überlastete" Lamda einen eigenen Erfassungsblock, dh diese Lambdas teilen nichts (und verschwenden wahrscheinlich CPU-Zeit damit, dieselben Daten immer wieder zu erfassen). Gibt es eine Chance, dass der C ++ - Standard etwas hat, um das zu korrigieren? Oder ist die einzige Option variadic generic lamda+ if constexpr, um Anrufe zu trennen?
CM
@CM Um eine Frage zum Stapelüberlauf zu stellen, klicken Sie oben rechts auf die Schaltfläche [Frage stellen] und nicht auf die Schaltfläche [Kommentar hinzufügen]. Vielen Dank!
Yakk - Adam Nevraumont