Lexical Conditionals

Here's a thing I'd love to see in C, but I never will.

// Suppose we have a known function pointer type...
typedef void(*callback)(int param);

// And some compatible function pointers...
struct callbacks {  
    callback if_foo;
    callback if_bar;
} my_callbacks = {...};

void invoke_callback(struct my_callbacks *cbs, bool is_foo, int param) {  
    cbs->(is_foo ? if_foo : if_bar)(param);
}

This doesn't work. People who understand C As She Is Spoke will understand clearly enough: neither possible result of the conditional ternary is an expression that can be evaluated on its own. The conditional is parsed as though if_foo and if_bar are standalone identifiers, and of course they're not.

But of course, if you look at the statement another way, it does make some sense. The presence of the dereference operator -> along with the subsequent grouped expression, seems to establish a lexical scope that needs to be resolved before parsing can continue.

And if seems were seams, I'd be Betsy Ross. You read the C spec and that's not how it works, and that's that. Can we gain any insight by trying to make it work another way?

callback cb;

// 1. Fully qualified expressions obviously work
cb = (is_foo ? cbs->if_foo : cbs->if_bar);

// 2. Pointer math
cb = (callback)(is_foo ? offsetof(struct my_callbacks, if_foo) : offsetof(struct my_callbacks, if_bar) + (uintptr_t)cbs)

// 3. Macro
#define field_select_cond(c, prefix, a, b) (c ? prefix->a : prefix->b)
cb = field_select_cond(is_foo, cbs, if_foo, if_bar)

// 4. Abomination
cb = (callback)((uintptr_t)my_callbacks + (is_foo ? offsetof(typeof(*my_callbacks), if_foo) : offsetof(typeof(*my_callbacks), if_bar)));  

The first one is how I actually do it. The second approach loses type information that you have to restore. I will admit to using the third approach or something like it, deep within macros that generate reams of boilerplate code. The fact that the fourth works at all is a tribute to how far the new C99 and C11 specifications have come compared to C89/90.

In fact, given that the fourth approach is valid, you could imagine this being implemented as a parser trick, the same way a function pointer cb invoked using cb(args) is actually converted to (*cb)(args) by some parsers.

But it gets ugly so fast. The inner scope can create shadowed identifiers simply by being created (e.g. what if struct my_callbacks contained a field named is_foo?) How do we handle this? We need some kind of token to indicate whether an identifier is part of the inner scope or the outer, indicating outer scope with something like C++'s ::.

Or...maybe that's the wrong approach. What if the outer scope is still default, and we specify inner scope the way we normally do?

void invoke_callback(struct my_callbacks *cbs, bool is_foo, int param) {  
    cbs(is_foo ? ->if_foo : ->if_bar)(param);
}

Like the proverbial regex-smith, now we have two problems. Except for the prefix dereference operators, we have a pretty clear expression, that says evaluate the conditional, call cb with the result of that, expect a function pointer as a result of that, then call the function pointer with the argument param. This is wrong on two counts, and this is where I leave well enough alone.

A final aside, for the sake of Monday morning quarterbacks: Was it a bad idea to use parentheses for all of the type cast operator, the function invocation operator and the statement grouping operator?

assert([[[[this] is] so] much: better]); // They tell me this is better.  
assert(array[object[array[[object] selector]]selector]] > 0); // WHY DID THEY LIE  

It's too late now. ¡We've used all the standard paired delimiters!