Intro
A “bad marketing” regarding lambdas in C++ was the use of the term “functions”. Ofcourse connaisseurs talked about “lambdas” “closures” or “lambda expressions” since lambdas are never quite functions in the codegen sense and we certainly can’t think of them as such in language semantics terms; yet this misconception can trigger a quest for features we normally find in plain or templated functions; the first and second sections of this post elaborate on this.
1. Lambda Overloading
If we think of lambdas as functions we’d might make an attempt to overload them. This attempt is easy to rebut by stating that lambdas are closures ie runtime objects and well … objects, even callable ones, do not overload! What is interesting here though, is that even lambdas “translatable” to functions (usually non capturing closures) when compiled have an intermediate representation of an object with at least 3 member functions : a function call operator, a conversion operator and one static member function, that simply invokes the lambda with this
set to zero; it would take some preconditions being satisfied (like the actual closure object not being used) to optimize the closure away and end up with a plain function call. What I mean by “can’t think of them as such (functions) in language semantics terms” is that even in this case we can’t expect this to work :
auto f = []( int){ cout << __PRETTY_FUNCTION__ << endl; }; auto f = [](char){ cout << __PRETTY_FUNCTION__ << endl; }; f( 1 ); f('a');
much like we can’t overload a function pointer: it’s always an ‘already declared‘ error. Since the title promises overloading, we’d better not call it a day yet. The closest thing to a lambda that can overload is its function call operator, so you might already had your “aha!” moment by now. If not, here it is :
Create an object out of the lambdas you want to overload and make their function call operators overloaded versions of the superobject’s function call operator.
The idea is often attributed to Mathias Gaunard as well as the following implementation:
template <class F1, class F2> struct overload_set : F1, F2 { overload_set(F1 x1, F2 x2) : F1(x1), F2(x2) {} using F1::operator(); using F2::operator(); }; template <class F1, class F2> overload_set<F1,F2> overload(F1 x1, F2 x2) { return overload_set<F1,F2>(x1,x2); }
The code can be described in a few simple steps
overload_set
is the superobject’s type; it inherits from the closure types we want to overload- the closure types’ function call operators are explicitly made visible to the scope of the class
overload
is a convenience function that returns an overload set
usage is straighforward :
auto f = overload ( []( int) { cout << __PRETTY_FUNCTION__ << endl; }, [](char) { cout << __PRETTY_FUNCTION__ << endl; } ); f('k'); f( 2 );
The names of the function call operators can be made available through ADL (argument dependent lookup) but the using
declarations of the original implementation were left unharmed since they make the code more explanatory. We’ll be removing them when we want to scale to arbitrary number of overload candidates (industrial-strength version is provided in the References section, link No 2):
template <class... F> struct overload_set : F... { overload_set(F... f) : F(f)... {} }; template <class... F> auto overload(F... f) { return overload_set<F...>(f...); }
Overloading lambdas gives you the full power of closures plus Ad-hoc polymorphism ie make lambdas (actually the superobject) behave differently for each type. You can imagine this being useful when writing a for_each
algorithm for tuples of different types; then the functor you pass to the algorithm can handle the different elements and be defined right at the location where it is called. In the following example we assume we have a function for_each_in_tuple
that visits every element in a tuple; we can then define “in situ” a mutator func
(an overload set of lambdas) that will transform the tuple in ways that vary depending on the element type:
int main() { auto func = overload ( [](int &val) { val *= 2; }, [](string &arg) { arg += arg; }, [](char &c) { c = 'x'; } ); tuple<int, string, char> tup {1, "boom", 'a'}; for_each_in_tuple(func, tup); cout << get<0>(tup) << endl << get<1>(tup) << endl << get<2>(tup); }
(Run the example here)
2. SFINAE
According to common knowledge generic lambdas are function objects with a templated function call operator. Back in the day when we mistook lambdas for functions we might attempt to use SFINAE on them, yet another language feature that works for function templates only. Using the above mechanism, expression SFINAE works out of the box for generic lambdas:
auto f = overload ( [](auto arg) -> enable_if_t< is_integral<decltype(arg)>::value> {cout << "1";}, [](auto arg) -> enable_if_t< !is_integral<decltype(arg)>::value>{cout << "2";} );
Easy peasy: Since function call operators are templated for generic lambdas they fall under SFINAE rules when the superobject’s function call operator triggers creating candidates for overload resolution.
3. Copyrights
Closures may generally be copied. There are cases though, where you want to prohibit this. For instance you may want to limit the ways a lambda can escape a scope in order to avoid dangling references. Silly example follows:
struct Locall { int val; ~Locall() { cout << "destroyed"; } }; int main() { function<void()> f; { Locall a{ 101 }; auto k = [re = ref(a)] { cout << "... but using value " << re.get().val; }; f = k; } f(); return 0; }
(run the example here)
If we marked k
as non copyable, the compiler wouldn’t allow f
to “absorb” a reference to an object about to die; it’s a way for the coder to convey intents that may later be forgotten (or can’t be grasped by another person working on your code). To make a non copyable lambda use copyrights :
auto k = [nocopy, re = ref(a)] { /* ... */ };
Since the obvious way for a lambda to be non copyable is to capture a move only type, I use a macro that generates an “init capture” (a C++14 generalized capture) with a unique name and a light(est)weight captured object. This is how:
#define CONCATENATE_DETAIL(x, y) x##y #define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) #define UNIQUE_IDEXPR(x) CONCATENATE(x, __COUNTER__) struct NC { NC() = default; NC(NC const&) = delete; NC& operator=(NC const&) = delete; NC(NC&&) = default; }; #define nocopy UNIQUE_IDEXPR(x) = NC()
Note that what we get from this is a unique expression (auto)xi = N()
(where auto isn’t actually spelled and i equals 1, 2, 3…), a move initialization due to the xvalue produced by NC()
ie requires the plain and move constructors to be available (auto generated or defined). Also xi
is in the scope of the closure class so we don’t need to worry about collisions with the outer scope
Quiz
Something is redundant in the nocopy
macro, can you spot it? Answer in the comments section or look at the code in Github (link No 2) to find out.
Lambda Overloading code does not in fact work with GCC as it is mentioned by a scrupulous user (http://cpptruths.blogspot.com/2014/05/fun-with-lambdas-c14-style-part-2.html?showComment=1399902243986#c2479252107123295916).
LikeLike
Worry not because if you click link No 2 in the References you’ll find that I use a more advanced implementation in the blog’s repository; that code is up for grabs! And it works wth gcc, clang and cl as well : http://coliru.stacked-crooked.com/a/932f6c367c641f2a . The code shown here was preffered for its brevity and simplicity; it has more disadvantages, like inability to perfect forward temporary lambdas (also addressed by the code in Github) but sure looks clear as crystal (ok and it works for clang and cl, I mean it’s not that bad – I should maybe edit to mention all this)
LikeLike
Hi there,
just one point: instead of using the macro, what about writing:
It’s just more readable and you don’t need macros at all. What do you think?
LikeLiked by 2 people
I’m personally ok with macros that take no arguments. What I aimed for was a composable tag system eg have
nocopy, nomove, nodel
with the ability to mix and match. That’s also achievable with your code so I think I consider your solution equivalent. On the upside the way you tweaked the code would be more easily acceptable as part of the standard library. What about a joined proposal on this ?LikeLike
I apologize, As Kirill pointed out, my code is broken. I think we still need the macro to generate a unique variable name.
LikeLike
@MarcoArena Full disclosure, I didn’t scrutinize the code. My reply was on your general view of composibility and the idea to achieve this through a single object mechanism, the
rights
structure, which I found pretty cool. What you wrote could be easily fixed in multiple ways, eg make the tags template arguments to your structand then go
like in this example http://coliru.stacked-crooked.com/a/0e7fb5c978cfb2da
LikeLike
Thanks Nick, your fix is fine and I think it’s even more readable than my original code. In general I think we still need the macro because of the need for a unique variable name in the lambda capture, but I don’t see any problem on this.
LikeLike
Hello Marco,
I feel sorry if I got your idea wrong but for me your code looks a bit broken.
The authors idea is a preventing lambda copying so error should happen on
f = k; line.
So we could fix your code using this:
struct rights
{
template
rights(T&&…){} // other rights if you need them… <- so we are OK to move r/xvalues
};
If we will stay with rights(T…) the deduced dype will be lvalue ref to NC (nocopy object) what is useless.
auto k = [rights(std::move(nocopy)), re = ref(a)] { /* … */ }; <- cast it to rvalue so rights structure will be perfectly fine fitted.
now in the flow is changed and we are OK with using k in nested scope but we will not be able to assign our lambda to function object.
LikeLike
Marco,
and by the way you don`t need struct rights definition, because what you are doing with this syntax is a defining a rights field of type NC inside lambda using init-capture syntax See 5.1.2.11 inside N4296
LikeLike
Hi Kirill, you are right, my code is broken for supporting other rights. I naively extended it. Thanks!
LikeLike
And yes, the syntax as well is wrong. I don’t know why I posted this code…
LikeLike
Macro Arena might be one rather cool coders name.
LikeLike
overload_set has enabled me to fulfill a not quite lifelong dream, but one I’ve had for a while now https://backwardsincompatibilities.wordpress.com/2015/10/26/visiting-without-travelling/
LikeLiked by 1 person
Reblogged this on xiangwordpresscom.
LikeLike