Reputation: 55564
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
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
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
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
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
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
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
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
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
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