'Overload a lambda function

How to overload a simple local lambda function?

SSE of original problem:

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

The error messages

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

Please don't mind not checking user input, this is an SSE.



Solution 1:[1]

No, you can not overload the lambda!

The lambdas are anonymous functors(i.e. unnamed function objects), and not simple functions. Therefore, overloading those objects not possible. What you basically trying to do is almost

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

Which is not possible, as the same variable name can not be reused in C++.


However, in we have if constexpr by which one can instantiate the only branch which is true at compile time.

Meaning the possible solutions are:

  • A single variabe template lambda. or
  • A generic lambda and find the type of the parameter using decltype for the if constexpr check.(credits @NathanOliver)

Using variabe template you can do something like. (See a 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];
    }
};

and call it like

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

Using generic lambda(since ), the above will be: (See a live demo 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];
    }
};

and call the lambda as you do now:

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

Solution 2:[2]

So the rules for overloading names only apply to certain kinds of lookup of function names (both free and methods).

Lambdas are not functions, they are objects with a function-call operator. So overloading cannot occur between two different lambdas.

Now, you can get overload resolution to work with function objects, but only within the scope of a single object. And then if there is more than one operator(), overload resoltion can pick between them.

A lambda, however, has no obvious way to have more than one operator(). We can write a simple (in ) utility class to help us:

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

and a deduction guide:

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

with these two, we can overload two lambdas:

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

and done.

Writing overloaded is possible in both and but requires more work and is less elegant. Once you are aware of the problem, finding a solution that matches what your particular compiler supports in the way of C++ features shouldn't be hard.

Solution 3:[3]

Lambdas are basically syntactic sugar for locally defined functors. As far as I know, they were never meant to be overloaded to be called with different parameters. Note that every lambda expression is of a different type, so even the immediate error aside, your code cannot work as intended.

You can however, define a functor with an overloaded operator(). This will be exactly what you'd get from lambdas if it was possible. You just dont get the terse syntax.

Something like:

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

Solution 4:[4]

It can be done using boost/hana/functional/overload

#include <boost/hana/functional/overload.hpp>
#include <iostream>
#include <map>

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


    auto translate = boost::hana::overload(
      [](int idx) {
          constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
          return table[idx];
      },
      [](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;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 PolyGlot