int j = 3;
int main()
{
int i = 3;
auto foo = [&](){ printf("%d\n", i); };
auto bar = [&](){ printf("%d\n", j); };
i = j = 4;
foo();
bar();
}
```
This changes the answer to "4" and then "4" ([https://godbolt.org/z/EW8PETdxz](https://godbolt.org/z/EW8PETdxz)). What this means is that this Quiz -- when properly displayed next to its counterparts -- shows that C++ Lambdas can be naturally configured to work like Apple Blocks OR like GNU Nested Functions, at the cost of one (1) character change in its capture clause. We can imagine that a person writing from the perspective of Apple Blocks could present the preceding C++ Lambda that doesn't have the same behavior they expect to be a "Quiz" that contains a big "Gotcha". The reality is that C++'s design can handle both defaults without compromising the ergonomics in any serious manner. The syntax for lambdas is, of course, "sinfully ugly" -- even C++ enthusiasts acknowledge this readily -- but the acknowlegement that there are engineering tradeoffs to be had -- and not things to poke fun at or make "gotcha"s out of -- is why the design is mature and useful.
In contrast, [[n3654]] proposes capturing and copying based on things such as whether or not a variable is `const`, which does not approximate how it works in **any** existing practice.
### Appendix A: List of Issues with C++ Lambdas ### {#appendix-nested.functions.context-appendix.a}
Appendix A is a laundry list of issues with C++ Lambdas, in no particular order. Some of them are already addressed in the introduction of Lambdas ([[#intro-lambdas]]), but in-general the list of issues has many flaws in its reasoning.
> - "Passing of a lambda as a regular parameter needs either trampolines or a new type."
Every single solution requires trampolines or a new type to be useful, GNU Nested Functions and Apple Blocks included. C++ did not have this problem because they have a much stronger base language that can do this as a library type: C needs a fundamental "wide function pointer" type no matter what ([[#appendix-wide.function.pointer]]) and it needs trampoline-making functionality ([[#appendix-make.trampoline]]).
> - Lambdas with lvalue capture suffer from the same lifetime limitations as nested functions. `auto foo(int i) { return [&](){ printf("%d\n", i); }; }`
This is discussed earlier in the introduction, but GNU Nested Functions have an identical problem. At the very least, Lambdas have a mechanism to stop this from being a problem: there is no built-in solution with GNU Nested Functions. Apple Blocks use an entire heap runtime and thus avoid this problem completely.
> - Not having destructors and smart pointers in C requires workarounds not needed in C++.
> - Not having explicit access to the structure holding the captured values requires unsafe byte copies, causes issues with alignment and makes deep copying impossible.
Everything in C is a byte copy, unless an explicit function is inserted to do something just before that byte copy happens. An example of this comes from Apple Blocks, with a required `Block_release` and `Block_copy` required to make usage of stored blocks safer ([[#intro-blocks-runtime-stack.based]]). To uphold this as a problem is to lambast the entirety of C and its object model as unsafe; which, honestly, is not an unfair assessment. The fix to that is to restore access to the objects captured inside of an object, as shown in [[#design-capture.functions-data.captures.fields]], which makes any capturing entity -- whether it's a `_Closure` like in [[n3654]], Capture Functions as in this paper, or Lambdas -- accessible once more.
> - Making the lambda itself have a unique anonymous object type in C means it can only be invoked immediately which seems useful only in macros. In C++ it can be returned from and passed to template functions.
This criticism is partly untrue: complete objects in C can have their type retrieved with `typeof`. That means it can be cast/assigned into heap storage, copied around, and called just fine in certain contexts (c.f. the "static trampoline" technique mentioned both in the paper and used extensively in the example code above). We also already have the `auto` type-specifier: as regular complete objects, such types can be created and then stored in what already exists as a feature in C23. Macros are a foundationally important use case: it means that expressions being passed into function-like macros can be evaluated once, and only once, by being passed to an immediately-invoked lambda expression.
Storage outside of those contexts has to be powered by a wide function pointer type, and this proposal acknowledges that it will be necessary to solve that problem (but not in this proposal). GNU Nested Functions also need such a type, as do Apple Blocks (though they do come with their own Block type as well using the `^` syntax). This is also partly discussed in [[#design-lambdas-returns]]. Actual returns were handled by [[n2923]] but only the `auto` for variable definitions was handled: functions was left for later, and there was initially consensus for something of this nature for the purposes of lambdas.
> - To address the various use cases, there are many different ways to capture variables `[&]`, `[=]`, `[]`, `[a]`, `[&b]`, `[a = b]`, `mutable`, etc. adding a lot of complexity that does not seem necessary.
The complexity is necessary, as demonstrated by the Quiz example in [[#appendix-nested.functions.context-appendix.b]]: the fact that captures can be changed and are not dependent on unrelated properties like `const`-ness (as in [[n3654]]) is how it successfully blends into e.g. the GNU ecosystem or the Apple ecosystem without breaking either of them. The complexity is inherent to the problem domain: glossing over it like GNU Nested Functions does limits whether or not this can successfully be deployed to replace Apple Blocks as an ISO C Standard solution.
> - Value captures can be confusing as they shadow the original variable under same name.
It is unclear how captures in which the user makes an explicit choice can be more confusing than one where the user has no choice but the behavior changes. We have already established this in the Apple Ecosystem perspective with Blocks versus the GNU Nested Functions perspective in [[#appendix-nested.functions.context-appendix.b]]; switching from one to another can result in bugs when people do not expect the default capturing style to change. Being explicit means nobody is surprised, and having renames prevents shadowing confusion: but it all has to be the user's choice.
> - Other features from C++ may need to be pulled in from C++ to make them fully useful, such as trailing return types, return type deduction, and generic arguments.
Trailing return types enhance what is capable, but are not strictly required ([[#design-lambdas-returns]]). Generic arguments are the one part that was opposed for inclusion in C23 and did not have consensus; it was, in fact, generic arguments that served as one of the primary reason Gustedt's Lambdas were completely and utterly tanked. This proposal does not use it and the design below does not require it to be useful, especially as anything relating to an immediately-invoked lambda in a macro can be covered by the necessary `typeof(...)` from C23.
> - Adopting lambdas from C++ would limit out design freedom relative to C++. We can not easily change specific aspects when it might be better for C, because it would then be a divergence from C++ that should be avoided and will be opposed by implementers.
As a sole solution, certainly. But this proposal provides Capture Functions ([[#design-capture.functions]]) as the flagship proposal for C, and maintains 1:1 identical capabilities with the proposed secondary Lambda part ([[#design-lambdas]]). There are also other reasons as to why having lambdas is good (particularly, for macros and for use as an expression). But, the general improvement here is that one can have Capture Functions that are rooted in C history and C syntax and C needs, while maintaining just enough of Lambdas that serve as a compatibility layer.
The fact that this paper is already proposing a depature from C++ for C lambdas and Capture Functions by having accessible data captures already shows we have the power to improve on things in C's favor, if we're willing to hold onto that ([[#design-capture.functions-data.captures.fields]]).
### Insufficient ### {#appendix-nested.functions.context-conclusions}
Given the state of [[n3654]], it seems like it has not sufficiently explored the consequences or implications of its proposed design, nor grounded it in sufficient existing practice for us to consider yielding to its principles. That does not mean all of the ideas are bad. In the above sections, after we repair some of the broken examples, there is clearly some potential in `_Closure` and the idea of an "environment" pointer. There is also perhaps merit in having a pointer that ties a specific function frame to a specific function call so that variable lookup that does not find a local variable can look in the "environment"/"`_Closure`" first before checking further surrounding variables (e.g., file-scope or `static` objects). But that is a separable problem -- and a lower-level problem -- that the tying of "function and its associated data".
A wide function pointer type ([[#appendix-wide.function.pointer]]) would be a far better pursuit, separately, even if none of the solutions here or in other proposals are achieved.
The paper also seems to be driven, largely, by three things:
- animus and disdain for C++ Lambdas and their design;
- a desire to formalize the (potentially advanced) uses of `__builtin_call_with_static_chain`;
- and, a strong preference for all of the design decisions of GNU Nested Functions.
It offers a "we can do these small things first" and then presents a wider narrative around how to handle the "data" part of "Functions with Data". Having a standardized solution that is less powerful than all of C++ Lambdas, GNU Nested Functions, Apple Blocks, and Jens Gustedt's proposed C Lambdas does not seem like a good or useful starting point. As much as WG14 as a Committee has many members that continue to extol the virtues of being slow, we believe that there has been significant existing practice and useful explanations of designs to move forward with something much more comprehensive and robust. Giving in to the temptation of "simplified GNU Nested Functions" with a somewhat incomplete and incoherent design plan based around the idea of `_Closure` after 30+ years of design work in this space from directly-related and applicable languages is not something we consider a good use of time.
We do not comment on the Polymorphic Types API because that is beyond what we consider the useful scope of what can or should be addressed in our current proposal.
## Wide Function Pointer Type ## {#appendix-wide.function.pointer}
[[n2862]], by Dr. Martin Uecker, is already looking into standardizing a wide function pointer type. A wide function pointer type is necessary in the general-purpose ecosystem, but isn't directly required to be tied to this proposal. Because it is a smaller entity, it can be put directly into the standard separately. We hope it's explored that rather than using `_Closure(function-type)` or `function-type _Wide` syntax, that `function-type%` is deployed as a usable syntax instead. This would simplify its use and its introduction:
```cpp
typedef int foo_fn_t(int);
foo_fn_t% call_me (int* x) {
return [x](int y) { return *x + y; };
}
int use_me(foo_fn_t% fn) {
return fn(2);
}
int main () {
int x = 30;
return use_me(call_me(&x));
}
```
In the above example, `foo_fn_t%` can be replaced with `_Closure(foo_fn_t)` or `foo_fn_t _Wide`; we prefer the former than the two latter for obvious grammatical and ease-of-use reasons. Most importantly, there is a canonical and viably implementable conversion path for not only whatever is standardized in ISO C, but all of the existing extensions such as Blocks, Nested Functions, C++ Lambdas, and language-external closure types.
NOTE: The caret (`^`) cannot be used for this purpose thanks to Apple and Objective-C/Objective-C++ taking that design space.
NOTE: The percent sign (`%`) does not conflict with Managed C++/CLI `ref` declarations that use `%` because naked `%` can only be applied to "value types" -- that is `struct` types. There is no callback type that fits this description in the garbage-collected .NET imperative language universe (C# or Managed C++/CLI); all callback types are declared as either raw function pointer types or `class`-based, "reference type" delegates in Managed C++/CLI.
## Make Trampoline and Singular Function Pointers ## {#appendix-make.trampoline}
In the later examples in [[#intro-nested.functions-alternative.implementations]], a magic compiler builtin named `__gnu_make_trampoline`, with a secondary follow-on builtin named `__gnu_destroy_trampoline`, is used. This section talks about what that would look like, if it was to be implemented. In particular, an ideal solution that makes a trampoline needs to be an explicit request from the user because:
- you want to opt-in to any dynamic allocations;
- you want to provide a way to override the default allocation if possible;
- and, you explicit control on when those resources (the allocation, the protected memory, and similar) are released.
While this section was spawned from GNU Nested Functions, this same technique can be used to make possible single function pointer trampolines for Blocks with or without captures ([[#intro-blocks]]) as well as C++-style Lambdas ([[#intro-lambdas]]).
Therefore, the best design to do this would be -- using the [[_Any_func]]* paper and its new type -- the following:
```cpp
typedef void* allocate_function_t(size_t alignment, size_t size);
typedef void deallocate_function_t(void* p, size_t alignment, size_t size);
_Any_func* stdc_make_trampoline(FUNCTION-WITH-DATA-IDENTIFIER func);
_Any_func* stdc_make_trampoline_with(
FUNCTION-WITH-DATA-IDENTIFIER func,
allocation_function_t* alloc
);
void stdc_destroy_trampoline(_Any_func* func);
void stdc_destroy_trampoline_with(_Any_func* func, deallocate_function_t* dealloc);
```
`stdc_make_trampoline(f)` would use some implementation-defined memory (including something pre-allocated, such as in Apple blocks ([[#intro-blocks-trampoline]])). The recommended default would be that it just calls `stdc_make_trampoline_with(f, aligned_alloc)`. `stdc_destroy_trampoline(f)` would undo, exactly, what `stdc_make_trampoline` would give. The recommended default would be that it is identical to `stdc_destroy_trampoline_with(f, free_aligned_size)`. Providing an allocation and a deallocation function means that while the implementation controls what is done to the memory and how it gets set up, the user controls where that memory is surfaced from. This would prevent the problem of the Heap Alternative Nested Function implementation: rather than creating a special stack or having to rely on memory allocation functions, the compiler can instead source the memory from a user. This also makes such an allocation explicit, and means that its lifetime could be Though, given our memory primitives, a slightly better implementation that would allow the implementation to take care of (potentially) extra space handed down by alignment and what not would be:
```cpp
struct allocation { void* data; size_t size; };
typedef allocation allocate_function_t (size_t alignment, size_t size);
typedef void deallocate_function_t (void* p, size_t alignment, size_t size);
_Any_func* stdc_make_trampoline(FUNCTION_TYPE func);
_Any_func* stdc_make_trampoline_with(FUNCTION_TYPE func, allocation_function_t* alloc);
void stdc_destroy_trampoline(_Any_func* func);
void stdc_destroy_trampoline_with(_Any_func*, deallocate_function_t* dealloc);
```
Regardless the form that the `make`/`destroy` functions take, this sort of intrinsic would be capable of lifting not just a typical GNU nested functions but all types of functions to be a single, independent function pointer with some kind of backing storage. Some desire may still exist to make the allocation and deallocation process automatic, but that should be left to compiler vendors to decide for ease-of-use tradeoffs versus e.g. security, like in [[#intro-nested.functions-design]].
It should be noted that Apple itself already has a version of this with this Objective-C Blocks Implementation ([[objective-c-block-trampoline]]), albeit with limitations discussed in [[#intro-blocks-trampoline]]. GCC does not expose an intrinsic for this per-se, but does provide `__builtin_call_with_static_chain` ([[builtin_call_with_static_chain_gcc|GCC Documentation: Builtin Call with Static Chain]]). One can build a trampoline mechanism overtop of that, provided they had the properly-created function plus the right stack frame / "environment" chain pointer to go with the function callable. Since C++ Lambdas -- and the proposed Capture Functions and C-Style Lambdas here -- are by themselves [[#design-capture.functions-complete.objects|Complete Objects]], one can always create a "thunk" or "trampoline" for them manually, using a wide variety of allowable techniques from heap allocation to pre-stored arrays to `_Thread_local`/`static` data or otherwise. C++ could implement `stdc_make_trampoline` entirely as a library function, but C cannot; so, this is something vendors will have to figure out on their own.
The only part that needs to be user-configurable is the source of memory. Of course, if an implementation does not want to honor a user's request, they can simply return a `(Any_func*)nullptr;` all the time. This would be hostile, of course, so a vendor would have to choose wisely about whether or not they should do this. The paper proposing this functionality would also need to discuss setting `errno` to an appropriate indicator after use of the intrinsic, if only to appropriately indicate what went wrong. For example, `errno` could be set to:
- `ENOMEM`: the allocation function call failed (that is, `alloc` returned `nullptr`).
- `EADDRNOAVAIL`: the address cannot be used for function calls (e.g., somehow being given invalid memory such as an address in `.bss`).
- `EINVAL`: `func` is a null function pointer or a null object.
- `EACCESS`: the address could be used for function calls but cannot be given adequate permissions (e.g., it cannot be succesfully `mprotect`d or `VirtualProtect`d).
to indicate a problem. Albeit, there are always complaints about `errno`, so it may also be possible to take an `int* p_errcode` parameter in the `make_trampoline` functions, and use that as a means of solving the problem (or swap the return type and the error code parameter to return the error code and output into an `_Any_func*`). The API design possibilities are, really, endless.
## Executable Stack CVEs ## {#appendix-executable.stack.cves}
ADVISEMENT: THIS SECTION IS INCOMPLETE.
The following CVEs are related to executable stack issues.
- [CVE 2017-100376: https://www.cve.org/CVERecord?id=CVE-2017-1000376](https://www.cve.org/CVERecord?id=CVE-2017-1000376)
- [CVE 2023-38408: https://www.cve.org/CVERecord?id=CVE-2023-38408](https://www.cve.org/CVERecord?id=CVE-2023-38408)
{
"_Any_func": {
"authors": [
"JeanHeyd Meneide",
"Shepherd (Shepherd's Oasis)"
],
"title": "_Any_func - A Universal Function Pointer Storage Type",
"date": "July 6th, 2025",
"href": "https://thephd.dev/_vendor/future_cxx/papers/C%20-%20_Any_func.html"
},
"builtin_call_with_static_chain_gcc": {
"authors": [
"GNU Compiler Collection Contributors",
"Free Software Foundation"
],
"title": "GCC Online Documentation: Constructing Calls",
"date": "May 3rd, 2025",
"href": "https://gcc.gnu.org/onlinedocs/gcc/Constructing-Calls.html#index-_005f_005fbuiltin_005fcall_005fwith_005fstatic_005fchain"
},
"nested-functions": {
"authors": [
"GNU Compiler Collection Contributors"
],
"title": "Nested Functions (Using the GNU Compiler Collection (GCC))",
"date": "May 3rd, 2025",
"href": "https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html"
},
"n2661": {
"authors": [
"Martin Uecker"
],
"title": "n2661: Nested Functions",
"date": "February 13th, 2021",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2661.pdf"
},
"n3654": {
"authors": [
"Martin Uecker"
],
"title": "n3654: Accessing the Context of Nested Functions",
"date": "July 20th, 2025",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3654.pdf"
},
"apple-blocks": {
"authors": [
"Apple & Contributors"
],
"title": "Documentation Archive: Declaring and Creating Blocks",
"date": "May 3rd, 2025",
"href": "https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Blocks/Articles/bxDeclaringCreating.html#//apple_ref/doc/uid/TP40007502-CH4-SW1"
},
"n1370": {
"authors": [
"Blaine Garst",
"Apple, Inc."
],
"title": "n1370: Apple Extensions to C",
"date": "March 10th, 2009",
"href": "https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1370.pdf"
},
"n1451": {
"authors": [
"Blaine Garst",
"Apple"
],
"title": "n1451: Blocks Proposal",
"date": "April 13th, 2010",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1451.pdf"
},
"n1457": {
"authors": [
"Blaine Garst",
"Apple"
],
"title": "n1457: Blocks",
"date": "April 20th, 2010",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1457.pdf"
},
"n2030": {
"authors": [
"Blaine Garst"
],
"title": "n2030: A Closure for C",
"date": "March 11th, 2016",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2030.pdf"
},
"clang-blocks-spec": {
"authors": [
"The Clang Team",
"LLVM and Contributors",
"Apple"
],
"title": "Clang + LLVM (Latest): Block Implementation Specification",
"date": "July 8th, 2025",
"href": "https://clang.llvm.org/docs/Block-ABI-Apple.html"
},
"swift-escapes": {
"authors": [
"Swift Development Team and Contributors",
"Apple"
],
"title": "The Swift Programming Language: Closures",
"date": "July 6th, 2025",
"href": "https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Escaping-Closures"
},
"objective-c-block-trampoline": {
"authors": [
"Objective-C Development Team and Contributors",
"Apple"
],
"title": "Objective-C Runtime / imp_implementationWithBlock ",
"date": "July 17th, 2025",
"href": "https://developer.apple.com/documentation/objectivec/imp_implementationwithblock(_:)?language=objc"
},
"n2862": {
"authors": [
"Martin Uecker",
"Jens Gustedt"
],
"title": "n2862: Wide Function Pointer Types for Pairing Code and Data ",
"date": "November 30th, 2021",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2862.pdf"
},
"n1229": {
"authors" : [
"Nick Stoughton"
],
"title": "Potential Extensions For Inclusion In a Revision of ISO/IEC 9899",
"date": "March 26th, 2007",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1229.pdf"
},
"transparent-aliases": {
"authors" : [
"JeanHeyd Meneide",
"Shepherd (Shepherd's Oasis, LLC)"
],
"title": "Transparent Aliases",
"date": "February 20th, 2025",
"href": "https://thephd.dev/_vendor/future_cxx/papers/C%20-%20Transparent%20Aliases.html"
},
"n2923": {
"authors" : [
"Jens Gustedt"
],
"title": "Type inference for variable definitions and function returns",
"date": "January 30th, 2022",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2923.pdf"
},
"n2924": {
"authors" : [
"Jens Gustedt"
],
"title": "Type-generic Lambdas",
"date": "January 30th, 2022",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2924.pdf"
},
"n2892": {
"authors" : [
"Jens Gustedt"
],
"title": "Basic lambdas for C",
"date": "December 25th, 2021",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2892.pdf"
},
"n2893": {
"authors" : [
"Jens Gustedt"
],
"title": "Options for Lambdas",
"date": "December 25th, 2021",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2893.htm"
},
"lambdas-nested-functions-block-expressions-oh-my": {
"authors" : [
"JeanHeyd Meneide"
],
"title": "Lambdas, Nested Functions, and Blocks, oh my!",
"date": "July 16th, 2021",
"href": "https://thephd.dev/lambdas-nested-functions-block-expressions-oh-my"
},
"gamingonlinux-dawe": {
"authors": [
"Liam Dawe"
],
"title": "The glibc 2.41 update has been causing problems for Linux gaming",
"date": "February 13th, 2025",
"href": "https://www.gamingonlinux.com/2025/02/the-glibc-2-41-update-has-been-causing-problems-for-linux-gaming/"
},
"wsl-no-executable-stack": {
"authors": [
"Microsoft",
"WSL Authors and Contributors",
"Martin Uecker"
],
"title": "fis-gtm does not run due to missing support for executable stack",
"date": "August 7th, 2018",
"href": "https://github.com/Microsoft/WSL/issues/286"
},
"solar-non-executable-stack-exploits": {
"authors": [
"solar FALSE COM (Solar Designer)"
],
"title": "Getting around non-executable stack (and fix)",
"date": "August 10th, 1997",
"href": "https://seclists.org/bugtraq/1997/Aug/63"
},
"n3645": {
"authors": [
"Thiago R. Adams"
],
"title": "n3645: Literal Functions",
"date": "July 11th, 2025",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3645.pdf"
},
"n3643": {
"authors": [
"Jakub Łukasiewicz"
],
"title": "n3643: Statement Expressions (draft)",
"date": "July 10th, 2025",
"href": "https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3643.htm"
},
"__self_func": {
"authors": [
"JeanHeyd Meneide",
"Shepherd (Shepherd's Oasis, LLC)"
],
"title": "__self_func",
"date": "February 11th, 2025",
"href": "https://thephd.dev/_vendor/future_cxx/papers/C%20-%20__self_func.html"
},
"thread-attributes": {
"authors": [
"JeanHeyd Meneide",
"Shepherd (Shepherd's Oasis, LLC)"
],
"title": "Thread Attributes - Implementation Extensible and ABI-Resistant",
"date": "July 6th, 2025",
"href": "https://thephd.dev/_vendor/future_cxx/papers/C%20-%20Thread%20Attributes%20-%20Implementation%20Extensible%20and%20ABI-Resistant.html"
}
}