#include <stdlib.h>
#include <string.h>
#include <stddef.h>
typedef int (*comp)(const void *untyped_left, const void *untyped_right);
thread_local int in_reverse = 0;
__attribute__((noinline))
int compare_impl(const void *untyped_left, const void *untyped_right, int in_reverse) {
const int* left = untyped_left;
const int* right = untyped_right;
return (in_reverse) ? *right - *left : *left - *right;
}
comp make_sort(int direction) {
in_reverse = direction;
int compare(const void *untyped_left, const void *untyped_right) {
return compare_impl(untyped_left, untyped_right, in_reverse);
}
return compare;
}
int main(int argc, char* argv[]) {
int list[] = { 2, 11, 32, 49, 57, 20, 110, 203 };
comp normal_sort = make_sort(0);
comp reverse_sort = make_sort(1);
qsort(list, (sizeof(list)/sizeof(*list)), sizeof(*list), normal_sort);
return list[0];
}
Because we create `reverse_sort` between creating `normal_sort` and calling it, we end up with a reverse sort despite clearly asking for a normal sort.Then it's clearly only half a solution.
The example I gave above should work fine in any language with first-class closures.
_Thread_local struct {
void *data;
int (*compare)(const void *a, const void*, void*);
} _qsort2_closure ;
static int _qsort2_helper(const void *a, const void *b) {
return _qsort2_closure.compare(a, b, _qsort2_closure.data);
}
void qsort2(void *base, size_t elements, size_t width, int (*compare)(const void *a, const void*, void*), void *userData)
{
_qsort2_closure.data = userData;
_qsort2_closure.compare = compare;
qsort(base, elements, width, _qsort2_helper);
}No I do not. It will reassigned next call.
> But again you are reinventing dynamic scoping
No. I’m not reinventing anything. I’m using the existing feature of thread local variables.
The usage of such is entirely an implementation detail of qsort2 with the exception of recursion.
Dynamic scoping typically refers to defining variables which have scope outside of their call stack. No usage of this API requires it.
Can you just try to learn something new?
Once again, the caller of the API does not declare any variables so there is no dynamic scoping.
It’s confusing to me that thread locals are “not the best idea outside small snippets” meanwhile the top solution is templating on recursion depth with a constexpr limit of 11.
(You could solve that with a manually maintained stack for the context in a thread local, but you'd have to do that case-by-case)
I think the times you need to do this are few. And this version is much more pruden.
Anyway, the larger point is that a re-entrant general solution is desirable. The sort example might be a bit misguided, because who calls sort-inside-sort[0]? Nobody, realistically, but these types of issues are prevalent in the "how to do closures" area... and In C every API does it slightly differently, even if they're even aware of the issues.
[0] Because there's no community that likes nitpicking like the C (or C++) community. I considered preempting that objection :). C++ has solved this, so there's that.
That you do not call it recursively by checking that the thread local is nil before invocation.
> a re-entrant general solution is desirable.
I know what you mean, but I just don't know why you want to emulate that in C. There is a real problem of people writing APIs that don't let you pass in data with your function pointer - the thread local method can solve 99% of those without changes to the original API.
But if you really want to do all kinds of first class functions with data, do you want to use C?