6

I am currently reading P0315R1 paper which talks about Lambdas in unevaluated contexts

There is a statement in the document which explains why lambdas can't appear in unevaluated contexts (of course only until C++20) as below:

Lambdas are a very powerful language feature, especially when it comes to using higher-order algorithms with custom predicates or expressing small, disposable pieces of code. Yet, they suffer from one important limitation which cripples their usefulness for creative use cases; they can’t appear in unevaluated contexts. This restriction was originally designed to prevent lambdas from appearing in signatures, which would have opened a can of worm for mangling because lambdas are required to have unique types.

Can somebody please explain this statement with an example?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
cpp_enthusiast
  • 1,239
  • 10
  • 28
  • related: [template with lambda as unique default parameter on each instantiation](https://stackoverflow.com/questions/54410522/template-with-lambda-as-unique-default-parameter-on-each-instantiation) – user7860670 Apr 17 '19 at 07:43
  • @VTT Thanks I will have a look at the link. – cpp_enthusiast Apr 17 '19 at 07:46
  • 3
    Read up on what name mangling means, and think about how that is supposed to apply to lambdas appearing in signatures. – Passer By Apr 17 '19 at 07:57
  • @PasserBy Thanks for your comment. I am already aware of what name mangling is. However, I have to understand how it applies to lambdas. Thanks for the good suggestion. – cpp_enthusiast Apr 17 '19 at 07:59

2 Answers2

9

A little background: Linkers don't understand function overloading - they only understand function names like in the C language. That's why C++ compilers mangle your function names. void foo(int) becomes _Z3fooi. The mangled name encodes the types of all the arguments. If your function resulted from a template instantiation, all template arguments get encoded too. If they are themselves template classes, their template arguments get encoded, and so on recursively until the primitive types like ints or function pointers are reached.

Lambdas make this challenging because each lambda is required to have distinct type. This isn't too bad for lambdas defined in functions:

auto foo() { return [](){}; }
auto bar() { return [](){}; }

foo and bar return different types. If you then pass them to another function template, that template's name is mangled as if with foo::__lambda1 or something to that effect.

Letting lambdas appear in decltype would break this mechanism.

void bar(decltype([](){}));
void bar(decltype([](){})) {}

Is this a prototype and a definition? Or are these two different overloads of bar? How to identify them across translation units (how to mangle the names)?

Until now, C++ forbid even asking this question. The paper you linked gives an answer: things like this can't have linkage. Don't even try to mangle them.

Filipp
  • 1,843
  • 12
  • 26
  • Your `bar`s need to be in a class so that the ODR unifies them (or else they could just be emitted with internal linkage anyway). – Davis Herring Apr 18 '19 at 02:13
  • Is that really true? They're not static and not in an anonymous namespace. – Filipp Apr 18 '19 at 09:42
  • It is true at the real tool (rather than abstract language definition) level: note the [`.globl` directives](https://godbolt.org/z/Tpfz4B). – Davis Herring Apr 18 '19 at 13:57
  • @Filipp I think I have to read a bit more on this. Can you point me in the direction of some documentation or references regarding this? – cpp_enthusiast Apr 20 '19 at 06:46
  • I can't, sorry. Try playing around with lambdas and templates on godbolt compiler explorer. Look at the full symbol names. Turn off demangling. Make intentional errors and see what the compiler prints. – Filipp Apr 21 '19 at 21:28
  • @Filipp I understand that declaring the `decltype([](){})` in the function signature creates an ambiguity for the compiler to differentiate between definition or overloads but I can't understand how it effects the name mangling. `void bar(decltype([](){}))` creates a unique mangled name right? The only problem is that the compiler can't call this function because each and every lambda has a unique type. – cpp_enthusiast Apr 24 '19 at 07:35
  • The main intention of the name mangling is to make it possible for linkers to distinguish between different versions of overloaded functions. If we have two functions `void bar(decltype([](){})){}` and `void bar(decltype([](){})){}` (Both are same), the compiler can distinguish between them because the every lambda produces a different type and then the compiler can use that unique type information to create unique mangled names, of course we can't call these functions unfortunately. Is there any thing I am missing here? – cpp_enthusiast Apr 24 '19 at 08:35
  • The compiler can't actually create unique mangled names for `bar`. What if another translation unit also declares `void bar(decltype([](){}){}` in the same namespace? Are they the same function? This sort of situation is why I think this was prohibited before C++20. Telling the compiler to just not mangle the lambdas lets us write `auto baz(auto x, auto y) -> decltype([x, y]() { /* complicated logic */ return result; }());` – Filipp Apr 24 '19 at 22:13
  • @Filipp I think compiler compiles only single translation unit at a time and doesn't know and doesn't care about other translation units. So ideally it creates a unique mangled name. But the problem comes when the linker try to merge the multiple definitions of the same entity (e.g. multiple definitions of the same inline function in a header file) based on these mangled name. correct me if my understanding is wrong. – cpp_enthusiast Apr 25 '19 at 06:17
  • You are correct. The compiler can't even in principle make a name unique across the entire program. – Filipp Apr 25 '19 at 12:03
0

If we have a inline function with decltype of lambda in the function signature in the header file. The compiler generates a unique mangled name for this function in every translation unit in which it was included.

So at the end the linker can't merge these multiple definitions of the "same" function because the mangled names differ.

cpp_enthusiast
  • 1,239
  • 10
  • 28