Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 277
snoopy Avatar asked Nov 04 '19 20:11

snoopy


People also ask

How do you overload a function?

You overload a function name f by declaring more than one function with the name f in the same scope. The declarations of f must differ from each other by the types and/or the number of arguments in the argument list.

Is it possible to overload a function?

Function overloading is a feature of object-oriented programming where two or more functions can have the same name but different parameters. When a function name is overloaded with different jobs it is called Function Overloading.

What does it mean to overload a function in C++?

C++ lets you specify more than one function of the same name in the same scope. These functions are called overloaded functions, or overloads. Overloaded functions enable you to supply different semantics for a function, depending on the types and number of its arguments.


2 Answers

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 c++17 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 c++14), 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]));
like image 183
JeJo Avatar answered Oct 08 '22 08:10

JeJo


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 c++17) 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 c++14 and c++11 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.

like image 36
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 08:10

Yakk - Adam Nevraumont