Jonathan.
Jonathan.

Reputation: 55564

C++ shorter lambda syntax

I'm new to C++, coming recently from Swift. Is there any way to get shorter lambda syntax?

I have a lot of lines like:

columns = {
    Col(_("Name"), "string", [] (Person *p) {
                                return p->Name();
                              }),
    Col(_("Age"), "int", [] (Person *p) {
                                return p->Age();
                              }),
    Col(_("Bank"), "string", [&banks] (Person *p) {
                            return banks.lookUp(p->Identifier()).Name;
                           }),
    //..etc..
};

Some of the columns require longer lambdas, but as it is the syntax for writing the lambda is about as long as the content it self.

Can the lambda syntax be reduced at all? (say by implicit argument or implicitly return the last statement)

Eg in Swift I could do something like this and it would be the same:

    Col(_("Age"), "int", { $0.Age() }),

EDIT: Added the Bank column as an example of a more complicated one.

Upvotes: 15

Views: 11158

Answers (9)

mnesarco
mnesarco

Reputation: 2788

There are amazing versions in the preceding answers, I have a less impressive solution but it is simple and works well for me (c++20):

// language: c++

#define LambdaBody(...)                             \
    noexcept(noexcept(__VA_ARGS__)) ->decltype(auto) \
    requires requires { __VA_ARGS__; }              \
    {                                               \
        return __VA_ARGS__;                         \
    }

#define Lambda0(...) \
    <typename T = void>() LambdaBody(__VA_ARGS__)

#define Lambda1(_1, ...) \
    ([[maybe_unused]] auto&& _1) LambdaBody(__VA_ARGS__)

#define Lambda2(_1, _2, ...)     \
    ([[maybe_unused]] auto&& _1, \
     [[maybe_unused]] auto&& _2) LambdaBody(__VA_ARGS__)

#define Lambda3(_1, _2, _3, ...) \
    ([[maybe_unused]] auto&& _1, \
     [[maybe_unused]] auto&& _2, \
     [[maybe_unused]] auto&& _3) LambdaBody(__VA_ARGS__)

#define Lambda4(_1, _2, _3, _4, ...) \
    ([[maybe_unused]] auto&& _1,     \
     [[maybe_unused]] auto&& _2,     \
     [[maybe_unused]] auto&& _3,     \
     [[maybe_unused]] auto&& _4) LambdaBody(__VA_ARGS__)

// ----------------------------------------------------------

#include <iostream>

int main(int i, char ** argv) {

    auto context = 0;

    auto demo0 = [&] Lambda0(++context);
    auto demo1 = [] Lambda1(x, x + 10);
    auto demo2 = [] Lambda2(x, y, x*y + 10);
    auto demo3 = [] Lambda3(x, y, z, x*y + z);

    std::cout << "demo0 = " << demo0() << "\n";
    std::cout << "demo0 = " << demo0() << "\n";
    std::cout << "demo1 = " << demo1(5) << "\n";
    std::cout << "demo2 = " << demo2(1, 2) << "\n";
    std::cout << "demo3 = " << demo3(1, 2, 3) << "\n";

}

See in compiler explorer: https://compiler-explorer.com/z/Gc5aofY6v

Upvotes: 0

Justin
Justin

Reputation: 25317

I made a terse lambda library to do this with a macro, provided that you can use C++20 (v0.1.1 supports C++17 with painful caveats). With this library, your code would be:

columns = {
    Col(_("Name"), "string", [] TL(_1->Name())),
    Col(_("Age"), "int", [] TL(_1->Age())),
    Col(_("Bank"), "string", [&banks] TL(banks.lookUp(_1->Identifier()))),
    //..etc..
};

This TL macro gives you an expression lambda similar to the { $0.Age() } Swift syntax, letting you access parameters with _1, _2, etc. Furthermore, this lambda is SFINAE-friendly and noexcept-friendly for those situations where that's desirable.

Note that TL's lambda returns by value like you did in your sample lambdas. If you wanted a decltype(auto) return type for your lambda, you can use the TLR macro instead.


I recommend caution if you want to use this library; using macros to alter the syntax of the language is a dangerous idea and can make your code hard to read.

Upvotes: 2

stgatilov
stgatilov

Reputation: 5533

Using C++14 and macros, you can closely mimick syntax of arrow functions from javascript (ES6).

In fact, it is usually enough to capture everything by reference. Argument type can be set to auto, return type can be omitted. So the shortest version that C++14 allows us is:

auto f = [&](auto x, auto y) { return x + y; };

Clearly, it can be easily turned into macros:

#define _L2(a, b, res) [&](auto a, auto b) { return res; }
#define _S2(res) _L2(_0, _1, res)

The _L2 macro creates a lambda with two user-specified parameters. The _S2 macro creates a lambda with parameters named _0 and _1. Lastly, we can use this answer to overload macro _L by number of arguments. I have no idea how to deduce number of arguments for _S via macros.

Now we can write something like:

auto g = _L(x, y, x + y);    //std::plus
auto g = _S2(_0 + _1);       //std::plus

You can even do some crazy things like:

//turns unary accessor function into binary comparator functor
auto compByKey = _L(f, _L(x, y, f(x) < f(y)));
//sort vector<string> by length
sort(arr.begin(), arr.end(), compByKey(_L(x, x.length())));

Indeed, the syntax is still not as clear as in the original javascript or swift, but it is much shorter. The problem now is to remember all the kinds of lambdas we have defined =) Anyway, STL library is not friendly to functional style programming...

Full code is available on ideone.

Upvotes: 9

hungptit
hungptit

Reputation: 1444

You could try the policy based design approach by defining your policy for each data member

enum FieldID {AGE, NAME, ID};
template<FieldID fid> auto get(Person &p);
template<FieldID fid> std::string getType();
template<FieldID fid> std::string getFieldName();

template <> auto get<AGE>(Person &p) {return p.Age();}
template <> auto get<NAME>(Person &p) {return p.Name();}
template <> auto get<ID>(Person &p) {return p.ID();}

template <> std::string getType<AGE>() {return "int";}
template <> std::string getType<NAME>() {return "string";}
template <> std::string getType<ID>() {return "size_t";}

template <> std::string getFieldName<AGE>() {return "Age";}
template <> std::string getFieldName<NAME>() {return "Name";}
template <> std::string getFieldName<ID>() {return "Bank";}

And your code will look like this

Col(_(getFieldName<AGE>()), getType<AGE>(), get<AGE>(p)

Upvotes: 0

Zereges
Zereges

Reputation: 5209

You could #define a macro for that, but it's a bit hackish and evil.

#define GEN_LAMBDA(arg) [](Person *p){ return p->arg();}

columns = {
    Col(_("Name"), "string", GEN_LAMBDA(Name)),
    Col(_("Age"), "int", GEN_LAMBDA(Age)),
    //..etc..
};

Upvotes: 0

R Sahu
R Sahu

Reputation: 206607

Can the lambda syntax be reduced at all?

I don't think so. The essential components of a lambda function for your needs are:

[ capture-list ] ( params ) { body }

You have it in as minimal a form as is possible with C++11.

Upvotes: 9

Richard Hodges
Richard Hodges

Reputation: 69892

if you have c++14 (and if you're coming from swift you probably do) then you can replace the argument type with auto. In addition, non-capturing lambdas require no space (they are equivalent to a function pointer) so there is no performance hit if you simply pre-define them and use a copy in your initialiser.

// this will return the Name of any pointee that has a Name() method
auto get_name = [](auto*p) { return p->Name(); }
auto get_age = [](auto*p) { return p->Age(); }

columns = {
    Col(_("Name"), "string", get_name),
    Col(_("Age"), "int", get_age),
    //..etc..
};

Upvotes: 2

ecatmur
ecatmur

Reputation: 157364

If you're always calling a member function, you can use mem_fn:

Col(_("Name"), "string", std::mem_fn(&Person::Name)),

A mem_fn works when passed either a pointer or a reference to the object type.

Upvotes: 14

Barry
Barry

Reputation: 303097

Instead of passing a lambda at all, you should rework Col_ so that it can accept pointers to members and will know what to do with them (e.g. by wrapping them in std::mem_fn). That way you can just write:

columns = {
    Col(_("Name"), "string", &Person::Name),
    Col(_("Age"), "int", &Person::Age),
    //..etc..
};

Upvotes: 4

Related Questions