Skip to main content

Stack Exchange Network

Stack Exchange network consists of 183 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.

Visit Stack Exchange
Asked
Viewed 132 times
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.

\$\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++.

\$\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\$
    J_H
    –  J_H
    2024-02-07 22:13:50 +00:00
    Commented Feb 7, 2024 at 22:13

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.