Function objects, and therefore lambdas, are not possible in C. However, if we think about one of the main benefits of lambdas – local variable capture – we can write both functions that can “capture” local variables, and functions that use those functions. Say for example you need to read from a socket. You can write the same socket reading routine each time, and handle all the errors and signals each time, handle the buffer management each time, or you can write a “generic” function that hides all the details:
int socket_read(int sock, bool (*func)(char*, ssize_t, va_list), ...) { ssize_t rc = 1024; char buffer[rc]; va_list args; va_start(args, func); while(!func(buffer, rc, args)) { va_end(args); va_start(args, func); errno = 0; rc = recv(sock, buffer, sizeof(buffer), MSG_PEEK); if(rc > 0) { rc = recv(sock, buffer, rc, 0); } else if(rc == 0) { break; } else if(rc == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { break; } } va_end(args); return rc; }
Note that this function also takes care of the va_args initialization and cleanup, making client code safer.
An example usage would be to have separate functions that reads, for example, HTTP headers, and one that reads HTTP payload:
bool read_header(char* buf, ssize_t len, va_list args) { ssize_t* hblen = va_arg(args, ssize_t*); ssize_t* hlen = va_arg(args, ssize_t*); char** headers = va_arg(args, char**); if(*hlen + len > *hblen) { if(!(*headers = (char*) realloc(*headers, *hlen + len))) // Replace naive memory management algorithm with more efficient one { return true; } *hblen = *hlen + len; } memcpy(*headers + *hlen, buf, len); *hlen += len; return strstr(*headers, "rnrn") != NULL; } bool read_payload(char* buf, ssize_t len, va_list args) { ssize_t* bytes_left = va_arg(args, ssize_t*); char** data = va_arg(args, char**); memcpy(*data, buf, len); *data += len; *bytes_left -= len; return bytes_left; }
Note that the read_header function has its own buffer management due to the variable nature of HTTP headers, but the read_payload function doesn’t need to because it would be used to read fixed size data which would be provided by outside code.
Note also how each of the functions can virtually communicate with itself through the pointers passed into it via the va_list, even though it has no direct control of the socket_read loop.
Note that you should also be mindful of the order of the varargs. The rule of thumb is the put the most important and/or independent variables first. eg, sizes should always come before the buffer they limit.
Now you can read from a socket much more elegantly:
int main() { int sock = socket(AF_INET, SOCK_STREAM, 0); // Set up socket // ... ssize_t hblen = 0; ssize_t hlen = 0; char* headers = NULL; // For the purposes of this example, rely on realloc(NULL,size) behaviour. In real use, preallocate, and set hblen to size socket_read(sock, read_header, &hblen, &hlen, &headers); // Note that read_header may read more than just the header // so find rnrn, and copy the rest of the valid contents // into the following payload buffer // and adjust begin accordingly ssize_t clen /* = Find and interpret Content-Length header */; char payload[clen]; char* begin = payload; socket_read(sock, read_payload, &clen, &begin); return 0; }
Note how you’ve effectively locally captured variables from the calling function to be passed on to the delegate functions. Note that I also don’t clutter the calling function with memory management.
- DISCLAIMER: I can vouch that the technique works, but since I rewrote code from scratch for this example and have not tested it due to time constraints, I cannot guarantee that the code works as is. It will need tweaking to get rid of bugs.