Sovraccarico di una funzione lambda


14

Come sovraccaricare una semplice funzione lambda locale?

SSE del problema originale:

#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;
}

I messaggi di errore

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

Per favore, non preoccuparti di non controllare l'input dell'utente, questo è un SSE.


7
Le lambda non sono funzioni, sono oggetti quindi il sovraccarico non si applica mai a loro. translatesono solo variabili locali che non possono riutilizzare lo stesso nome.
user7860670

Risposte:


10

No, non puoi sovraccaricare la lambda!

Le lambda sono funzioni anonime (ovvero oggetti funzione senza nome) e non semplici funzioni. Pertanto, non è possibile sovraccaricare quegli oggetti. Quello che sostanzialmente stai cercando di fare è quasi

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

Ciò non è possibile, poiché lo stesso nome di variabile non può essere riutilizzato in C ++.


Tuttavia, in abbiamo per if constexprmezzo del quale si può istanziare l'unico ramo che è vero al momento della compilazione.

Ciò significa che le possibili soluzioni sono:

  • Un unico modello lambda variabe . o
  • Un lambda generico e trova il tipo di parametro usando decltype per il if constexprcontrollo. (Credits @NathanOliver )

Usando il modello variabe puoi fare qualcosa di simile. ( Guarda una demo live 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];
    }
};

e chiamalo come

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

Usando lambda generico (dal ), quanto sopra sarà: ( Guarda una demo live online )

#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];
    }
};

e chiama il lambda come fai ora:

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

3
Lo trovo incredibile
snoopy

1
Innanzitutto, else ifdevi esserlo else if constexpr. In secondo luogo, perché usare un modello variabile? Potresti semplicemente rendere la lambda generica e i tuoi checls diventerebbero if constexpr (std::is_same_v<decltype(idx), int>)eelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver

6

Le lambda sono fondamentalmente zucchero sintattico per i funzionari definiti localmente. Per quanto ne so, non sono mai stati pensati per essere sovraccaricati per essere chiamati con parametri diversi. Si noti che ogni espressione lambda è di un tipo diverso, quindi anche a parte l'errore immediato a parte, il codice non può funzionare come previsto.

È comunque possibile definire un funzione con un sovraccarico operator(). Questo sarebbe esattamente ciò che otterresti da lambdas se fosse possibile. Non si ottiene la sintassi concisa.

Qualcosa di simile a:

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;
}

aspetta un minuto, stai chiamando sintassi lambda bello ??
user7860670

1
@VTT è bello che la sintassi sia concisa. Rispetto ad alcune cose più antiche non è poi così male
idclev 463035818

5

Pertanto, le regole per il sovraccarico dei nomi si applicano solo a determinati tipi di ricerca dei nomi delle funzioni (sia gratuiti che metodi).

Le lambda non sono funzioni, sono oggetti con un operatore di chiamata di funzione. Quindi non può verificarsi un sovraccarico tra due diversi lambda.

Ora puoi ottenere la risoluzione del sovraccarico per lavorare con oggetti funzione, ma solo nell'ambito di un singolo oggetto. E poi se ce n'è più di uno operator(), la risoluzione del sovraccarico può scegliere tra di loro.

Una lambda, tuttavia, non ha modo ovvio di averne più di una operator(). Possiamo scrivere una semplice classe di utilità (in ) per aiutarci:

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

e una guida alla detrazione:

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

con questi due possiamo sovraccaricare due lambda:

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]));

e fatto.

La scrittura overloadedè possibile sia in che ma richiede più lavoro ed è meno elegante. Una volta a conoscenza del problema, trovare una soluzione che corrisponda a ciò che il tuo particolare compilatore supporta in termini di funzionalità C ++ non dovrebbe essere difficile.


A quanto ho capito, ogni lamda "sovraccaricata" ha il proprio blocco di acquisizione, cioè quei lambda non condividono nulla (e probabilmente sprecano il tempo della CPU catturando sempre gli stessi dati). Qualche possibilità che lo standard C ++ abbia qualcosa per rimediare? O l'unica opzione è variadic generic lamda+ if constexprper separare le chiamate?
CM

@CM Per porre una domanda sull'overflow dello stack, premere il pulsante [Poni domanda] in alto a destra, non il pulsante [Aggiungi commento]. Grazie!
Yakk - Adam Nevraumont il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.