Перевантажуйте лямбда-функцію


14

Як перевантажити просту функцію локальної лямбда?

SSE вихідної проблеми:

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

Повідомлення про помилки

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

Будь ласка, не забувайте не перевіряти введення користувача, це SSE.


7
Лямбди не є функціями, вони є об'єктами, тому перевантаження їх ніколи не стосується. translate- це лише локальні змінні, які не можуть повторно використовувати те саме ім’я.
користувач7860670

2
пов'язаний з / боян: stackoverflow.com/questions/32475576 / ...
NathanOliver

Відповіді:


10

Ні, не можна перевантажувати лямбда!

Лямбди - це анонімні функтори (тобто об'єкти без назви функцій), а не прості функції. Тому перевантажувати ці об’єкти неможливо. Те, що ви в основному намагаєтеся зробити, - це майже

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

Що неможливо, оскільки одне й те саме ім’я змінної неможливо повторно використовувати в C ++.


Однак у ми маємо if constexprможливість інстанціювати єдину гілку, істинну під час компіляції.

Можливі рішення:

  • Єдиний шаблон змінної лямбда. або
  • Узагальнена лямбда і знайдіть тип параметра, використовуючи decltype для if constexprперевірки. (Кредити @NathanOliver )

Використовуючи шаблон varijabe, ви можете зробити щось на кшталт. ( Дивіться демо-версію в режимі онлайн )

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

і називати це так

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

Використовуючи загальну лямбда (оскільки ), вище буде: ( Дивіться демонстрацію в режимі реального часу в Інтернеті )

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

і зателефонуйте лямбда, як це робите зараз:

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

3
Я вважаю це дивовижним
snoopy

1
По-перше, ваші else ifпотреби else if constexpr. По-друге, навіщо використовувати шаблон змінної? Ви можете просто зробити лямбда родовою, а ваші чеки стануть if constexpr (std::is_same_v<decltype(idx), int>)іelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver

6

Лямбди - це в основному синтаксичний цукор для локально визначених функторів. Наскільки я знаю, вони ніколи не повинні були перевантажуватися, щоб викликатись з різними параметрами. Зауважте, що кожен лямбда-вираз має різний тип, тому навіть негайна помилка в стороні вашого коду не може працювати за призначенням.

Однак можна визначити функтор з перевантаженим operator(). Це саме те, що ви отримаєте від лямбдасів, якби це було можливо. Ви просто не отримуєте короткий синтаксис.

Щось на зразок:

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

зачекайте хвилинку, ви називаєте синтаксис лямбда приємним ??
користувач7860670

1
@VTT приємно, що синтаксис стислий. У порівнянні з деякими більш давніми речами це не дуже погано
idclev 463035818

5

Таким чином, правила перевантаження імен застосовуються лише до певних видів пошуку імен функцій (як вільних, так і методів).

Лямбди - це не функції, це об'єкти з оператором виклику функцій. Тому перевантаження не може відбуватися між двома різними лямбдами.

Тепер ви можете отримати дозвіл на перевантаження для роботи з функціональними об'єктами, але тільки в межах одного об'єкта. І тоді, якщо їх більше operator(), можна вирішити перевантаження між ними.

Однак лямбда не має очевидного способу мати більше одного operator(). Ми можемо написати простий (у ) клас корисності, щоб нам допомогти:

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

і керівництво відрахуванням:

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

з цими двома ми можемо перевантажити дві лямбда:

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

і зробили.

Писання overloadedможливе як у і але вимагає більше роботи та менш елегантна. Коли ви знаєте про проблему, пошук рішення, яке відповідає тому, що підтримує ваш конкретний компілятор за допомогою функцій C ++, не повинно бути складним.


Як я розумію, у кожної "перевантаженої" ламди є власний блок захоплення, тобто ті лямбдати нічого не ділять (і, ймовірно, витрачають час процесора на збирання одних і тих же даних знову і знову). Будь-який шанс стандарту C ++ буде чимось виправити це? Або єдиний варіант - variadic generic lamda+ if constexprрозділити дзвінки?
CM

@CM Щоб задати питання щодо переповнення стека, натисніть кнопку [Задати питання] угорі праворуч, а не кнопку [Додати коментар]. Дякую!
Якк - Адам Невраумон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.