3

I want to implement in C something similar to lambda functions from C++( using macros and function pointers)

I think the biggest problem that I am facing is to define a function inside another function, and this thing is not possible in C. I think a better idea is to treat the lambda function ( passed through a macro ) as a lexical block.

I started some code:

#define func_name(line) func##line

#define line __LINE__

#define lambda(body, ret_type, ...)   ret_type  func_name(line)(__VA_ARGS__)  \
                                 {                     \
                                   body;               \
                                 }                     \


//#include <stdio.h>

lambda(  printf("Hello from Lambda\n") ,  void, int a, float b)



int main(void) {
  //printf("Hello World\n");
  return 0;
}

I used gcc compiler with "-E" option to see preprocessor output:

  void funcline(int a, float b) { printf("Hello from Lambda\n"); }

  int main(void) {

  return 0;
  }
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • 1
    not sure why you're trying to do this...I would reframe your study after you have a nice system in place for calling out to arbitrary functions (addresses) – yano Nov 11 '21 at 17:16
  • 1
    A lexical block doesn't have a value, so you can't assign the result to anything. – Barmar Nov 11 '21 at 17:17
  • 5
    The *main* draw of lambda expressions is defining a callable thing within another function. Because you can't do that in C, I wouldn't bother. Just write your functions elsewhere – Caleth Nov 11 '21 at 17:23
  • Do you have anything against declaring free-standing functions and passing function pointers? – Thomas Matthews Nov 11 '21 at 17:35
  • Many smart people have tried things like this and the results have never been very satisfactory. If you really need lambdas then you need a language that is not C. If compiler-specific extensions are an option, GCC has nested functions, but they require runtime support that can be problematic (e.g. executable trampolines on the stack). – Nate Eldredge Nov 11 '21 at 17:35
  • I think you should give an example. e.g.: How do you want to call your lambda? Do you want to be able to pass it as an argument? Do you need it to have lexical scope? What are your requirements? How "similar" to C++ lambda functions do you need this to be? What is it about lambda functions that you want to make use of in your C code? – Wyck Nov 11 '21 at 17:37
  • 1
    @Caleth, The main draw is that they are *closures*, capturing the local scope. Put differently, we use them because it's a convenient way of attaching data to a function. (It's basically the flip side of objects, which attach functions to data.) I'm not sure how this could be done in C without coordination on the part of the caller. – ikegami Nov 11 '21 at 17:45
  • You could possibly do something horrible with `setjmp` and `longjmp` if the lambda code will only be called during a call to the function where it's defined. – aschepler Nov 11 '21 at 18:23
  • @NateEldredge, actually there is proposal to add c++ like lambdas to C. See http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2638.pdf It is not a technical problem but rather philosophical. It requires automatic type deduction that add a lot of hidden mechanics what is against C principle about being explicit – tstanisl Nov 11 '21 at 18:26
  • A lambda in C++ is quite equal to the creation of a single anonymous object of an anonymous class that stores its captures in member variables and provides an appropriate `()` operator. I see it like syntactic sugar; I had to use lambdas in an old version of C++. Oh, and Java, look at swing's listeners. -- Anyway, you can try to mimic this with a struct for the captures and a function pointer, but the called function has to be defined free-standing. -- However, why at all do you want to do this in C? – the busybee Nov 11 '21 at 18:34
  • 1
    Re "*However, why at all do you want to do this in C?*", Cause it's a crazy strong feature. – ikegami Nov 11 '21 at 19:16
  • I feel sorry for the poor soul trying to debug this. Just use C++ if you wish to do this – Ed Heal Nov 11 '21 at 20:31

1 Answers1

4

It's possible, but the lambdas will have to be stateless (no captures). You can use preprocessor to move function definitions around, but there is no way to use local variables from one function in another, or to introduce some kind of state into a function.

Also it makes the code very hard to debug. Since all functions using those pseudo-lambdas have to be wrapped in a macro, all line breaks in them are removed during preprocessing. It becomes impossible to place a breakpoint inside of such a function, or to advance through it line by line.


Here is an example of the usage. The implementation is at the end of the answer. The explanation of the syntax is right after the example.

Run on gcc.godbolt.org

#include <stdio.h>
#include <stdlib.h>

ENABLE_LAMBDAS(

void example1()
{
    int arr[] = {4,1,3,2,5};

    FUNC(int)(compare)(const void *a, const void *b)
    (
        return *(int*)a - *(int*)b;
    )
    qsort(arr, 5, sizeof(int), compare);

    for (int i = 0; i < 5; i++ )
        printf("%d ", arr[i]);
    putchar('\n');
}

void example2()
{
    int arr[] = {4,1,3,2,5};

    qsort L_(arr, 5, sizeof(int), LAMBDA(int)(const void *a, const void *b)
    (
        return *(int*)a - *(int*)b;
    ));

    for (int i = 0; i < 5; i++ )
        printf("%d ", arr[i]);
    putchar('\n');
}

int main()
{
    example1();
    example2();
}

) // ENABLE_LAMBDAS

Notice the ENABLE_LAMBDAS macro wrapping the whole snippet.

This example uses two ways of defining functions/lambdas:

  • FUNC(return_type)(name)(params)(body) just defines a function. The function definition is moved to the beginning of ENABLE_LAMBDAS, so it can be used inside of other functions.
  • LAMBDA(return_type)(params)(body) defines a pseudo-lambda. A function definition for it is generated at the beginning of ENABLE_LAMBDAS, with an automatically chosen unique name. LAMBDA... expands to that name.

If FUNC or LAMBDA are used inside of parentheses, the parentheses must be preceeded by the L_ macro. This is a limitation of the preprocessor, unfortunately.

The generated functions are always static.


Implementation:

// Creates a lambda.
// Usage:
//     LAMBDA(return_type)(params)(body)
// Example:
//     ptr = LAMBDA(int)(int x, int y)(return x + y;);
#define LAMBDA LAM_LAMBDA

// Defines a function.
// Usage:
//     FUNC(return_type)(name)(params)(body)
// Example:
//     FUNC(int)(foo)(int x, int y)(return x + y;)
//     some_func(foo);
#define FUNC LAM_FUNC

// Any time a `LAMBDA` or `FUNC` appears inside of parentheses,
//   those parentheses must be preceeded by this macro.
// For example, this is wrong:
//     foo(LAMBDA(int)()(return 42;));
// While this works:
//     foo L_(LAMBDA(int)()(return 42;));
#define L_ LAM_NEST

// `LAMBDA` and `FUNC` only work inside `ENABLE_LAMBDAS(...)`.
// `ENABLE_LAMBDAS(...)` expands to `...`, preceeded by function definitions for all the lambdas.
#define ENABLE_LAMBDAS LAM_ENABLE_LAMBDAS

// Lambda names are composed of this prefix and a numeric ID.
#ifndef LAM_PREFIX
#define LAM_PREFIX LambdaFunc_
#endif

// Implementation details:

// Returns nothing.
#define LAM_NULL(...)
// Identity macro.
#define LAM_IDENTITY(...) __VA_ARGS__
// Concats two arguments.
#define LAM_CAT(x, y) LAM_CAT_(x, y)
#define LAM_CAT_(x, y) x##y
// Given `(x)y`, returns `x`.
#define LAM_PAR(...) LAM_PAR_ __VA_ARGS__ )
#define LAM_PAR_(...) __VA_ARGS__ LAM_NULL(
// Given `(x)y`, returns `y`.
#define LAM_NO_PAR(...) LAM_NULL __VA_ARGS__
// Expands `...` and concats it with `_END`.
#define LAM_END(...) LAM_END_(__VA_ARGS__)
#define LAM_END_(...) __VA_ARGS__##_END

// A generic macro to define functions and lambdas.
// Usage: `LAM_DEFINE(wrap, ret)(name)(params)(body)`.
// In the encloding code, expands to `wrap(name)`.
#define LAM_DEFINE(wrap, ...) )(l,wrap,(__VA_ARGS__),LAM_DEFINE_0
#define LAM_DEFINE_0(name) name,LAM_DEFINE_1
#define LAM_DEFINE_1(...) (__VA_ARGS__),LAM_DEFINE_2
#define LAM_DEFINE_2(...) __VA_ARGS__)(c,

// Creates a lambda.
// Usage: `LAM_LAMBDA(ret)(params)(body)`.
#define LAM_LAMBDA(...) LAM_DEFINE(LAM_IDENTITY, __VA_ARGS__)(LAM_CAT(LAM_PREFIX, __COUNTER__))
// Defines a function.
// Usage: `LAM_FUNC(ret)(name)(params)(body)`.
#define LAM_FUNC(...) LAM_DEFINE(LAM_NULL, __VA_ARGS__)

// `LAM_LAMBDA` and `LAM_FUNC` only work inside of this macro.
#define LAM_ENABLE_LAMBDAS(...) \
    LAM_END( LAM_GEN_LAMBDAS_A (c,__VA_ARGS__) ) \
    LAM_END( LAM_GEN_CODE_A (c,__VA_ARGS__) )

// Processes lambdas and functions in the following parentheses.
#define LAM_NEST(...) )(open,)(c,__VA_ARGS__)(close,)(c,

// A loop. Returns the original code, with lambdas replaced with corresponding function names.
#define LAM_GEN_CODE_A(...) LAM_GEN_CODE_BODY(__VA_ARGS__) LAM_GEN_CODE_B
#define LAM_GEN_CODE_B(...) LAM_GEN_CODE_BODY(__VA_ARGS__) LAM_GEN_CODE_A
#define LAM_GEN_CODE_A_END
#define LAM_GEN_CODE_B_END
#define LAM_GEN_CODE_BODY(type, ...) LAM_CAT(LAM_GEN_CODE_BODY_, type)(__VA_ARGS__)
#define LAM_GEN_CODE_BODY_c(...) __VA_ARGS__
#define LAM_GEN_CODE_BODY_l(wrap, ret, name, ...) wrap(name)
#define LAM_GEN_CODE_BODY_open() (
#define LAM_GEN_CODE_BODY_close() )

// A loop. Generates lambda definitions, discarding all other code.
#define LAM_GEN_LAMBDAS_A(...) LAM_GEN_LAMBDAS_BODY(__VA_ARGS__) LAM_GEN_LAMBDAS_B
#define LAM_GEN_LAMBDAS_B(...) LAM_GEN_LAMBDAS_BODY(__VA_ARGS__) LAM_GEN_LAMBDAS_A
#define LAM_GEN_LAMBDAS_A_END
#define LAM_GEN_LAMBDAS_B_END
#define LAM_GEN_LAMBDAS_BODY(type, ...) LAM_CAT(LAM_GEN_LAMBDAS_BODY_, type)(__VA_ARGS__)
#define LAM_GEN_LAMBDAS_BODY_c(...)
#define LAM_GEN_LAMBDAS_BODY_l(wrap, ret, name, par, ...) static LAM_IDENTITY ret name par { __VA_ARGS__ }
#define LAM_GEN_LAMBDAS_BODY_open()
#define LAM_GEN_LAMBDAS_BODY_close()
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • This is pretty elaborate. In which context was this developed? (Don't tell me you did it ad hoc answering the question!) – Peter - Reinstate Monica Nov 17 '21 at 17:56
  • This is horrible; and I'd say should never be used. Btw, there [was a lambdas proposal for C23](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2633.pdf), don't know where it stands. – Yakov Galka Nov 17 '21 at 18:03
  • @Peter-ReinstateMonica It was ad hoc. :) The underlying trick (how to loop over sequences with the preprocessor) is the same as in ["duplicate function defintion with and without const" macro](https://stackoverflow.com/a/58466360/2752075), or the kind of macros that define a struct/enum with an associated reflection metadata. – HolyBlackCat Nov 17 '21 at 19:20
  • @YakovGalka Interesting proposal. But my gut feeling is one should keep the language plain and simple; that's its allure, for compiler writers and users alike. In any case it would be important to keep it a subset of C++ lambdas. – Peter - Reinstate Monica Nov 17 '21 at 22:39