For fun I decided to try to emulate lambda functions in plain old C. It turns out it can be easily done with a bit of macro abuse.
CLambda
struct stores function pointer to the function and has its own "stack" for capturing local variables. Function signature is erased in the process and there is no type checking when calling it, so you can easily mess things up.
But other than that, I think it turned out quite nicely.
clambda.h
#ifndef CLAMBDA_H
#define CLAMBDA_H
#include <assert.h>
#include <stdint.h>
#include <string.h>
typedef struct CLambda CLambda;
typedef void (*CLambdaFn)(CLambda *lambda, ...);
#define CLAMBDA_STACK_SIZE 52
typedef struct CLambda {
CLambdaFn fn;
int32_t captureSize;
/* Captured variables */
uint8_t stack[CLAMBDA_STACK_SIZE];
} CLambda;
// static_assert(sizeof(CLambda) == 64, "");
#define CLAMBDA(lambdaName) \
(CLambda) { \
.fn = (CLambdaFn) lambdaName \
}
#define CLAMBDA_BEGIN_CAPTURE(lambda) \
do { \
CLambda *_lam = (lambda); \
_lam->captureSize = 0; \
} while (0)
#define CLAMBDA_CAPTURE(lambda, var) \
do { \
CLambda *_lam = (lambda); \
int32_t _stackSize = _lam->captureSize; \
int32_t _varSize = sizeof(var); \
assert(_stackSize + _varSize < sizeof(_lam->stack)); \
void *_location = &(var); \
memcpy(&_lam->stack[_stackSize], _location, _varSize); \
_lam->captureSize += _varSize; \
} while (0)
#define CLAMBDA_UNPACK_START() int32_t _stackOffset = 0
#define CLAMBDA_UNPACK(type, name) \
type name = (_stackOffset += sizeof(type), \
assert(_stackOffset <= _lam->captureSize), \
*(type *) &_lam->stack[_stackOffset - sizeof(type)])
#define CLAMBDA_CAST_RET(retType) (retType(*)(CLambda *, ...))
#define CLAMBDA_CALL(lambda, retType, ...) \
((CLAMBDA_CAST_RET(retType)((lambda)->fn))(lambda, ##__VA_ARGS__))
#define CLAMBDA_DECL(lambdaName, ...) \
lambdaName(CLambda *_lam, ##__VA_ARGS__)
#endif // CLAMBDA_H
main.c
#include <stdio.h>
#include "clambda.h"
int CLAMBDA_DECL(lambdaAdd, int a) {
CLAMBDA_UNPACK_START();
CLAMBDA_UNPACK(int, x); // still have access to x
return a + x;
}
void CLAMBDA_DECL(printArgs) {
CLAMBDA_UNPACK_START();
CLAMBDA_UNPACK(int, argc);
CLAMBDA_UNPACK(char **, argv);
printf("Program arguments:\n");
for (int i = 0; i < argc; i++) {
printf("\t%s\n", argv[i]);
}
}
CLambda foo(int x) {
CLambda lam = CLAMBDA(lambdaAdd);
CLAMBDA_BEGIN_CAPTURE(&lam);
CLAMBDA_CAPTURE(&lam, x);
// x goes out of scope
return lam;
}
int main(int argc, char **argv) {
CLambda lambda1 = foo(8);
printf("%d\n", CLAMBDA_CALL(&lambda1, int, 2));
CLambda printLam = CLAMBDA(printArgs);
CLAMBDA_BEGIN_CAPTURE(&printLam);
CLAMBDA_CAPTURE(&printLam, argc);
CLAMBDA_CAPTURE(&printLam, argv);
CLAMBDA_CALL(&printLam, void);
return 0;
}
To run it:
gcc -Wall -Wpedantic -std=c99 main.c -o lambda
./lambda
As a bonus, with GNU extensions you can also nest functions for a much nicer syntax, but at the cost of less portable code.