eĿlipsis
a language independent preprocessor
 
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Loading...
Searching...
No Matches
ellipsis-lambda.h File Reference

Add a rudimentary lambda feature to gnu C23. More...

Go to the source code of this file.

Macros

#define CAPTURE(...)
 Freeze the values of a set of variables in some secret place.
 
#define LAMBDA(RET, ...)
 Define a lambda with return type RET and argument list __VA_ARGS__
 
#define RECAP(...)   __VA_OPT__(RECAP_(CAPTURE_NAME(__CAPTURE_COUNT__), __VA_ARGS__))
 Restore a set values frozen by CAPTURE for the current scope.
 

Detailed Description

Add a rudimentary lambda feature to gnu C23.

Warning
This feature needs nested functions under the hood, so it may provoke allergic reactions for some and it doesn't work with clang.

Macro Definition Documentation

◆ CAPTURE

#define CAPTURE (   ...)

Freeze the values of a set of variables in some secret place.

The use of CAPTURE and RECAP usually comes in pairs

CAPTURE(argc, argv);
auto λ = LAMBDA(double, double x) {
RECAP(argc, argv);
... do something with argc or argv ...
return argc*x;
};
#define LAMBDA(RET,...)
Define a lambda with return type RET and argument list __VA_ARGS__
Definition ellipsis-lambda.h:7
#define CAPTURE(...)
Freeze the values of a set of variables in some secret place.
Definition ellipsis-lambda.h:3
#define RECAP(...)
Restore a set values frozen by CAPTURE for the current scope.
Definition ellipsis-lambda.h:4

Here λ will use the frozen value of argc whenever it is called and possible modifications that have been inflicted to argc are ignored by these calls.

Warning
Be careful when capturing array values. This mechanism simply generates a pointer to the original array and does not freeze the values of the array elements. Also the length of the array would not be accessible in the lambda.
Remarks
One capture can serve several RECAP invocations; all RECAP refer to the latest CAPTURE that is in their scope.
The type of the variables after RECAP is const-qualified.
RECAP may be used at any inner scope that needs to restore the frozen values, it is not limited to lambdas.
A RECAP invocation always has to be in another, inner, scope, otherwise we would have a redeclaration of the variables in the list.

◆ LAMBDA

#define LAMBDA (   RET,
  ... 
)

Define a lambda with return type RET and argument list __VA_ARGS__

Other than a function definition, a lambda is an expression that can be used everywhere where function pointer could be used.

The following lambda

LAMBDA(double, double x){
return 2.0*x + 1;
}

as if an anonymous function were defined somewhere and then the expression would have been replace with a pointer to that function. So a use of that expression in an assignment

y = LAMBDA(double, double x){
return 2.0*x + 1;
}(35);

has some function

double SOME_WEIRD_NAME(double x) {
return 2.0*x + 1;
}

defined in some place, and then replaces the assignment above as if it were written as

y = &SOME_WEIRD_NAME(35);

This is by itself as presented here is not too interesting.

One point where it becomes interesting is when encapsulated inside a macro that provides a type-generic feature:

#define MIN(X, Y) \
LAMBDA(typeof((X)+(Y)), typeof(X) _X, typeof(Y) _Y){ \
return (_X < _Y) ? _X : _Y; \
}

The other point that makes this form of lambda interesting is that the lambda has access to local variables of the enclosing function.

double sumup(size_t n, double A[n], double fact) {
auto λ = LAMBDA(double, double x) {
return fact*x;
};
double accu = 0.0;
for (size_t i = 0; i < n; ++i) {
accu += λ(A[i]);
}
return accu;
}

Here λ has access to fact at each call. Such a feature is difficult to reproduce with a "normal" function, somehow we'd have to know how to provide the information to λ.

The model of access that is used by this implementation of LAMBDA is that an access to the original variable is made at each call. If for example we'd change fact between two calls, the second call would see the updated value. We even could modify fact from within λ, but that is generally considered to be bad style.

If we want to be sure that the lambda always uses the value of the variable as it is at the point where the lambda is defined, we can use a combination of the CAPTURE and RECAP macros to freeze the values of the used outer variables in place.

double sumup2(size_t n, double A[n], double fact) {
CAPTURE(fact);
auto κ = LAMBDA(double, double x) {
RECAP(fact);
return fact*x;
};
double accu = 0.0;
for (size_t i = 0; i < n; ++i) {
accu += κ(A[i]);
}
return accu;
}

Here the invocation of CAPTURE stores the current value of fact in a safe and immutable place, and then RECAP accesses that stored value and makes it available under the name fact within κ, again. Note that the variable fact in κ has the same type as the function argument, but that it is in addition const-qualified and thus cannot be changed.

See also
CAPTURE

◆ RECAP

#define RECAP (   ...)    __VA_OPT__(RECAP_(CAPTURE_NAME(__CAPTURE_COUNT__), __VA_ARGS__))

Restore a set values frozen by CAPTURE for the current scope.

See also
CAPTURE