3

I'm currently learning C, and trying to make my code more readable and easy to write, I found myself in need of creating a function (closure) in a function, and returning that function. Here's what I'm trying to do:

#typedef int (int_predicate*)(int);
int_predicate equals(int x) {
  int ret(int y) { return x == y; }
  return ret;
}

Now, this, doesn't work, and I get it: I'm creating this closure (ret) inside a function, so once this function returns, the pointer is no longer relevant, because it was defined on the stack. This is similar to if I did it for a pointer for a simpler type:

int* bad_int_ptr_function() {
  int intOnTheStack;
  int* ptr = &intOnTheStack;
  return ptr;
}

So, how can I achieve what I want - creating a closure in a function and returing it? If the size of the type pointed at, was known, I'd be able to use malloc, and if I wanted to, I could follow it with a memcpy. But, I can only presume that the size of a function can't be found with a sizeof, because a function can have any number of lines.

This is C, I can't use syntax or libraries that are specific to C++ (I've heard C++ recently-ish got lambda expressions - if only I could use C++, but I can't, this if for a C-specific course).

uvero
  • 81
  • 1
  • 1
  • 9
  • 4
    Note that defining nested functions in any shape or form, closure or not, isn't possible in standard C. See also [Nested function in C](https://stackoverflow.com/q/2608158/11082165) – Brian61354270 Dec 20 '22 at 20:47
  • 1
    I expect GCC’s feature to define nested functions will not support any method of exporting a nested function to be used outside the containing function. This is because it uses space on the stack to implement the nested function (possibly including putting executable code on the stack), so that space becomes unreserved when the containing function returns. Clang’s [Blocks](https://clang.llvm.org/docs/BlockLanguageSpec.html) feature might be able to do this. – Eric Postpischil Dec 20 '22 at 20:47
  • @Brian: Defining nested functions is possible in standard (conforming) C, because the C standard allows and invites extensions. It is not possible in strictly conforming C. But, of course, very little is possible in strictly conforming C, such as calling `write` or other operating system routines. – Eric Postpischil Dec 20 '22 at 20:48
  • An alternative might be to define a nested function and then call the functions that are going to use it from the containing function. – Eric Postpischil Dec 20 '22 at 20:50
  • 2
    @EricPostpischil There's still a difference. If `write` is available from a library (or even required by another standard such as POSIX), you probably will not find a C compiler which does not allow calling it on that platform. However, an extension supportong nested functions is not going to be quite so ubiquitous on any platform. – hyde Dec 20 '22 at 20:53
  • This can not really be done in C. Well, it can *kinda* be done, but not in a convenient way. If you want to do stuff like this, choose another language. – klutt Dec 20 '22 at 20:57
  • @hyde: That is a difference of availability, not of whether something is standard C or not. And it is a dubious difference; nested functions are available wherever GCC is available, even if `write` is not, and `write` is available on any Unix system even if nested functions are not (if there is any Unix system without GCC or a compiler without support for nested functions, which might be rarer than systems without `write`). It is at most an arbitrary distinction based on the preferences of the speaker, not one rooted in the C standard. – Eric Postpischil Dec 20 '22 at 20:57
  • If you want closures, you don't want C. C does not support closures (lambdas) as such — even in C23, AFAICS. – Jonathan Leffler Dec 20 '22 at 20:57
  • @EricPostpischil Are you sure a strictly conforming implementation does not support libraries? But even if it doesn't, it's possible to provide header-only version of `write` (probably dummy), when it is not available from system libraries. If a compiler doesn't support GNU C nested functions, you can't make the C code to compile for it. – hyde Dec 21 '22 at 05:10
  • @hyde: C 2018 4 5 says “A *strictly conforming program* shall use only those features of the language and library specified in this document…” `write` cannot be written in strictly conforming C, since it requires interactions with hardware not specified by the C standard (even just a C wrapper to a system call is not specified by C, since it requires some sort of system-call instruction). So even if you link `write` into your program, the resulting program is not strictly conforming C. – Eric Postpischil Dec 21 '22 at 12:40
  • @hyde: The only programs that can be implemented in strictly conforming C are those that do simple input and output and abstract computations. E.g., you could do some mathematical analysis, some physics modeling, and play a text adventure game. You cannot use a GUI, call operating system routines, or communicate on the network. – Eric Postpischil Dec 21 '22 at 12:44
  • @EricPostpischil I think the distinction I'm after is, can the single source file be compiled as strictly conforming C code or not. I believe `{ ssize_t write(int fd, const void *buf, size_t count); write(0, 0, 0); }` will compile, while code with nested functions will not compile. I think compiling vs not compiling is quite significant difference. – hyde Dec 21 '22 at 13:34
  • @hyde: `ssize_t` is not strictly conforming. Even if it were, what is the ultimate effect of making a distinction about whether the first point that code cannot serve as strictly conforming is when it compiles, when it links, or when it executes? Ultimately, you are choosing to use a non-portable feature. Any such choice is subject to the needs and desires of its maker. Recall that what started this was a statement that something is not “standard C.” But the C standard invites extensions and generally defines them as conforming code. It has only two levels, conforming and strictly conforming… – Eric Postpischil Dec 24 '22 at 00:14
  • … (and, by logical necessity, non-conforming). Both `write` and nested functions are conforming but not strictly conforming. Any further distinctions are outside of the C standard. – Eric Postpischil Dec 24 '22 at 00:15

2 Answers2

1

In Standard C, you cannot define closures as functions, but you can emulate them using data structures:

#include <stdio.h>

typedef struct int_predicate_t {
    int (*func)(struct int_predicate_t *s, int x);
    int y, z;
} int_predicate_t;

#define APPLY(p, x)  ((p).func(&(p), x))

int equal_func(struct int_predicate_t *s, int x) {
    return x == s->y;
}

int greater_func(struct int_predicate_t *s, int x) {
    return x > s->y;
}

int between_func(struct int_predicate_t *s, int x) {
    return x >= s->y && x <= s->z;
}

int_predicate_t equals(int y) {
    int_predicate_t s = { equal_func, y, 0 };
    return s;
}

int_predicate_t greater(int y) {
    int_predicate_t s = { greater_func, y, 0 };
    return s;
}

int_predicate_t between(int y, int z) {
    int_predicate_t s = { between_func, y, z };
    return s;
}

int main() {
    int_predicate_t p1 = equals(42);
    int_predicate_t p2 = greater(10);
    int_predicate_t p3 = between(1, 100);

    printf("p1(42) = %d\n", APPLY(p1, 42));
    printf("p2(42) = %d\n", APPLY(p2, 42));
    printf("p3(42) = %d\n", APPLY(p3, 42));
    printf("p1(0) = %d\n", APPLY(p1, 0));
    printf("p2(0) = %d\n", APPLY(p2, 0));
    printf("p3(0) = %d\n", APPLY(p3, 0));

    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

There's no (easy, robust) way in C to create closures.*

But one way you can come somewhat close: you can return a struct that contains a function pointer that can take the struct or its address as an argument:

typedef int ( *intClosureFuncPtr )( struct intClosure * );

struct intClosure
{
    int tag // can be used to differentiate multiple struct
            // types in a union
    intClosureFuncPtr func;
        .
        .
    // fields as necessary
};

Closure functions:

int closureFunc0( struct intClosure *c )
{
       .
       .
       .
    return( 0 );
}

int closureFunc42( struct intClosure *c )
{
       .
       .
       .
    return( 42 );
}

Calling the function that creates the closure and returns the struct:

struct intClosure someFunc( ... )
{

    struct intClosureInstance = { 0 };

    intClosure.tag = 7;
    intClosure.func = closureFunc42;
    // fill in rest of the struct fields as needed 

    return( intClosureInstance );
}

Usage:

struct intClosure closureInstance = someFunc(...);
    .
    .
    .

// call the closure:
int result = closureInstance.func( &closureInstance );

It's also possible to malloc() the struct and return it by address, but that requires the caller to free() it. You'd need to assess the size of your "closure" struct and having to pass it by value against the extra complexity of requiring the caller to free() it.

Note that the return type of this "closure" is fixed. There are hacks that would likely allow dynamic selection of return types on POSIX and Windows systems since those both have one flat address space shared (current Windows does...) by all variables and functions, but those hacks would be diving into behavior that's not defined by the C standard.

* - If you're willing to limit C "closures" to being called only from the thread they're created in and to only one outstanding "closure" per thread at any instant in time, you could use thread-specific data to pass information to an actual function that's returned by function pointer when the closure is generated, and that function could then be called directly without any indirection via the above struct method. And there may be ways to allow for more complex usage patterns, but I'm not able to come up with any at the moment.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56