Перегрузка лямбда-функции

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это просто локальные переменные, которые не могут использовать одно и то же имя.
user7860670
2
related / dupe: 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возможность создавать единственную ветвь, которая является истинной во время компиляции.

Имеются в виду возможные решения:

  • Один шаблон variabe лямбда. или
  • Общая лямбда и найти тип параметра, используемого decltype для if constexprпроверки. ( Credits @NathanOliver )

Используя шаблон variabe вы можете сделать что-то вроде. ( Смотрите живую демоверсию онлайн )

#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]));
JeJo
источник
3
Я нахожу это удивительным
Снупи
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;
}
idclev 463035818
источник
подождите минутку, вы называете лямбда-синтаксис хорошим ??
user7860670
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 Чтобы задать вопрос о переполнении стека, нажмите кнопку [Задать вопрос] справа вверху, а не кнопку [Добавить комментарий]. Спасибо!
Якк - Адам Невраумонт