15

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.

Jonathan.
  • 53,997
  • 54
  • 186
  • 290
  • 1
    If you are using the same lambda over and over you can store it in a variable and then use the variable in place of the lambda. – NathanOliver Apr 21 '16 at 16:16
  • _"Can the lambda syntax be reduced at all?"_ Well ask the [ISO c++ commitee](https://isocpp.org/std/the-committee). – πάντα ῥεῖ Apr 21 '16 at 16:17
  • @NathanOliver, I know, but they are not the same lambda? – Jonathan. Apr 21 '16 at 16:19
  • @πάνταῥεῖ, I'm not asking like that, I mean like the `-> Type` for the return type is optional is anything else? – Jonathan. Apr 21 '16 at 16:21
  • 2
    If you're always planning to call `Col` and give it member functions, you could have it take a pointer-to-member rather than an ordinary function. Then you could call them with `Person::Age` as the argument. – Silvio Mayolo Apr 21 '16 at 16:21
  • How about a macro: `#define L(body, ...) [__VA_ARGS__](){body;}`. Usage: `L(return p->Name())`, `L(return banks.lookUp(p->Identifier()).Name, banks)` – Kerrek SB Apr 21 '16 at 16:30
  • [Standards proposal for abbreviated lambdas](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0573r0.html) – Justin Jul 15 '17 at 04:48

9 Answers9

14

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 13
    Jesus Christ, how could they name it `mem_` instead of `member_`?! Like a flashback from the `creat` days of UNIX (or `if exist` of DOS). Except that this also carries an extra cognitive cost to first always having to undo the "mem = memory" association every time one sees it. And it also makes it impossible to generalize to `member_data`, or just `member`, in case it may wish to happen in the future (imagine those with `mem`!). – Sz. Aug 20 '19 at 11:42
  • 4
    @Sz. I thought until this moment that `mem_` was the abbreviation of `memory_`, which didn't make any sense to me... – Simone Rondelli Dec 07 '20 at 20:55
9

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.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
8

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.

Community
  • 1
  • 1
stgatilov
  • 5,333
  • 31
  • 54
  • 2
    Great answer, and a good example how macros can be used beneficially, especially in functional contexts. `#define` is often too unpopular due to the damage it _may_ cause. Yet, `map(vec, [] (int e) { return 2*e + 1; })` is so much more cumbersome to read than something like `map(vec, L( 2*it + 1 ))`. What you need to consider however is that identifiers starting with `_` and an uppercase letter are **reserved for implementation**. – TheOperator Aug 21 '19 at 21:08
  • 1
    Great answer, but I suggest accepting arguments as `auto&&` to minimize copying. – Andrei Matveiakin Aug 26 '21 at 19:53
4

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..
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Yup i considered that, but it would only work for when the simple ones. I can't really post more because I'm not allowed to post the actual source code(like the actual code has nothing to do with People) – Jonathan. Apr 21 '16 at 16:23
  • @Jonathan. Why would it "only work" for the simple ones? I didn't say *only* accept pointers to members, just extend it to work for pointers to members too. – Barry Apr 21 '16 at 16:42
2

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..
};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

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.

Justin
  • 24,288
  • 12
  • 92
  • 142
0

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..
};
Community
  • 1
  • 1
Zereges
  • 5,139
  • 1
  • 25
  • 49
0

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)
hungptit
  • 1,414
  • 15
  • 16
  • Currently each column is represented by an enum, and the problem is some columns are generated dynamically based on other data. – Jonathan. Apr 21 '16 at 21:45
0

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

mnesarco
  • 2,619
  • 23
  • 31