3
\$\begingroup\$

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.

Toby Speight
87.2k14 gold badges104 silver badges322 bronze badges
asked Jan 3, 2024 at 11:04
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

This is terrible, for several reasons.

  • Lambdas are anonymous functions. Here nothing is anonymous.
  • It's not type-safe. While C is not that great with type safety to begin with, this actually erases the type of the function, and you have to ensure you pass the correct return type and parameter types to CLAMBDA_CALL(). If you don't it will still compile, but result in unexpected behavior when running the program. The same goes for the captures.
  • The need to use lots of macros makes it look horrible.

I'm sorry to be so negative, but I think this is a case of: you should not want to do this. Either write what you want in plain C without using macros, which I think isn't even significantly more lines of code than what you have here in main.c, or use a programming language that does support lambdas, like C++.

answered Feb 7, 2024 at 21:06
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Yup. Right tool for the right job. Just because you can make a Turing machine do anything doesn’t mean you should tell it to do that. \$\endgroup\$ Commented Feb 7, 2024 at 22:13

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.