Przeciąż funkcję lambda

14

Jak przeciążyć prostą lokalną funkcję lambda?

SSE oryginalnego problemu:

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

Komunikaty o błędach

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

Nie przejmuj się sprawdzaniem danych wejściowych użytkownika, to jest SSE.

wścibski
źródło
7
Lambdy nie są funkcjami, są obiektami, więc przeciążanie nigdy ich nie dotyczy. translatesą tylko lokalnymi zmiennymi, które nie mogą ponownie użyć tej samej nazwy.
user7860670,
2
powiązane / dupe: stackoverflow.com/questions/32475576/…
NathanOliver

Odpowiedzi:

10

Nie, nie możesz przeciążać lambda!

Lambdas to anonimowe funktory (tj. Nienazwane obiekty funkcyjne), a nie proste funkcje. Dlatego przeciążenie tych obiektów nie jest możliwe. W zasadzie próbujesz to zrobić

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

Nie jest to możliwe, ponieważ tej samej nazwy zmiennej nie można ponownie użyć w C ++.


Jednak w mamy if constexprinstancję jedynej gałęzi, która jest prawdziwa w czasie kompilacji.

Znaczenie możliwych rozwiązań to:

  • Pojedynczy szablon vardabe lambda. lub
  • Ogólna lambda i znajdź typ parametru używanegodecltype do if constexprkontroli. (Kredyty @NathanOliver )

Za pomocą szablonu variabe możesz zrobić coś takiego. ( Zobacz prezentację online na żywo )

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

i nazwać to tak

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

Używając ogólnej lambda (od ), powyższe będzie: ( Zobacz demo na żywo 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];
    }
};

i zawołaj lambda tak jak teraz:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
JeJo
źródło
3
Uważam to za niesamowite
snoopy
1
Po pierwsze, else ifmusisz być else if constexpr. Po drugie, po co stosować zmienny szablon? Możesz po prostu uczynić lambda ogólną, a twoi checlowie staną się if constexpr (std::is_same_v<decltype(idx), int>)ielse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver
6

Jagnięta są zasadniczo składniowym cukrem dla lokalnie zdefiniowanych funktorów. O ile mi wiadomo, nigdy nie miały być przeciążone, aby wywoływać je różnymi parametrami. Zauważ, że każde wyrażenie lambda jest innego typu, więc nawet pomijając natychmiastowy błąd, twój kod nie może działać zgodnie z przeznaczeniem.

Można jednak zdefiniować funktor z przeciążeniem operator(). Będzie to dokładnie to, co dostaniesz od lambdas, jeśli to możliwe. Po prostu nie otrzymujesz zwięzłej składni.

Coś jak:

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
źródło
poczekaj chwilę, nazywasz się ładną składnią lambda?
user7860670,
1
@VTT fajnie, że składnia jest zwięzła. W porównaniu z niektórymi
starszymi
5

Zatem zasady przeciążania nazw dotyczą tylko niektórych rodzajów wyszukiwania nazw funkcji (zarówno wolnych, jak i metod).

Lambdy nie są funkcjami, są obiektami z operatorem wywołania funkcji. Zatem przeciążenie nie może wystąpić między dwoma różnymi lambdami.

Teraz możesz uzyskać rozdzielczość przeciążenia do pracy z obiektami funkcyjnymi, ale tylko w zakresie pojedynczego obiektu. A jeśli jest ich więcej operator(), rozwiązanie przeciążenia może wybierać między nimi.

Jednak lambda nie ma oczywistego sposobu na posiadanie więcej niż jednego operator(). Możemy napisać prostą (w ) klasę użyteczności, która pomoże nam:

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

oraz przewodnik dedukcyjny:

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

dzięki tym dwóm możemy przeciążyć dwa 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]));

i zrobione.

Pisanie overloadedjest możliwe zarówno w i ale wymaga więcej pracy i jest mniej eleganckie. Gdy zdasz sobie sprawę z problemu, znalezienie rozwiązania, które pasuje do tego, co obsługuje Twój kompilator, w zakresie funkcji C ++, nie powinno być trudne.

Jak - Adam Nevraumont
źródło
Jak rozumiem, każda „przeładowana” lamda ma swój własny blok przechwytywania, tzn. Te lambdy nic nie współużytkują (i prawdopodobnie marnują czas procesora na przechwytywanie tych samych danych w kółko). Czy jest jakaś szansa, że ​​standard C ++ będzie miał coś do naprawienia? Czy tylko opcja variadic generic lamda+ if constexprumożliwia oddzielne połączenia?
CM
@CM Aby zadać pytanie na temat przepełnienia stosu, naciśnij przycisk [Zadaj pytanie] w prawym górnym rogu, a nie przycisk [Dodaj komentarz]. Dzięki!
Yakk - Adam Nevraumont