In a previous post of mine, I talked about how javascript and some other languages have an OR operator that doesn’t cast to a boolean.
The non-coalescing operator is extremely useful for setting default text. For example in JS:
textField.text = username.value || fullName.value || "No Name Entered";
In C#, the operator is ??, the Null Coalescing Operator
In PHP and C (only certain extensions though) the operator is ?:, the shorthand ternary operator. This was introduced into PHP in version 5.3.
I am not sure when GCC got the extension.
To get similar behavior to ECMAScript, one could do, int value = x ?: y ?: z ?: a ?: b ?: c;
This only works with arithmetic types in C, and strings/numbers which fall under primitives in PHP.
We still need a solution if we want to do something similar for objects/strings in C.
A reader informed me that my previous implementation does not short-circuit like the native operator would.
I am all about performant code, and I failed to provide that. I apologize to anyone who might be using that implementation. Short-circuiting is a huge performance gain and should be a part of an elegant functional OR macro.
So I went back to the drawing board. I looked into recursive variadic macros and recursive variadic C++ templates. I needed to be able to take any number of arguments, as well as varied types. The technique listed here for compile-time Factorial, Template metaprogramming, looked interesting. I tried to replicate the compile-time struct{} declaration but it turns out that a template’s parameters
After ruling out templates as a fruitful approach, I looked back into macros. Macros can’t recurse in C. Even if they have a fixed point, they still can’t recurse, C doesn’t support it. Instead you have to define MACRO_1, MACRO_2, … MACRO_N, in order to do pseudo-recursion. Quite ugly as you can see here: http://stackoverflow.com/a/5048661
Boost provides a nice way around this. With BOOST_PP_SEQ_FOLD, we can pass an initial state as an argument and use a sequence to layer over that initial state until the sequence is consumed.
By using BOOST_PP_SEQ_FOLD_RIGHT, we can make OR(a,b,c)
turn into (isEmpty(a) ? (isEmpty(b) ? c : b) : a)
The BOOST_PP_SEQ_FOLD_RIGHT hands us the step, state, and current sequence element. The macro we pass to BOOST_PP_SEQ_FOLD_RIGHT to run over our argument sequence is:
#define __OR(s, state, obj) (isEmpty(obj) ? state : obj)
We fold from the right instead of the left, because the right-most argument shouldn’t be checked for empty, and it needs to be the deepest-nested.
With an isEmpty function overloaded for the many different primtive/object types, and a generic one for the id type, we get the code below:
#define BOOST_PP_VARIADICS 1 #include <boost/preprocessor/seq.hpp> #include <boost/preprocessor/variadic/to_seq.hpp> #define __OR(s, state, obj) (isEmpty(obj) ? state : obj) #define _OR(seq) BOOST_PP_SEQ_FOLD_RIGHT(__OR, BOOST_PP_SEQ_ELEM(BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(seq)),seq), BOOST_PP_SEQ_POP_BACK(seq)) #define OR(...) _OR(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) /* We define a slew of overloaded isEmpty functions for cases where we know the type at compile-time If the type cannot be deduced at compile time, the slower isEmpty(id) is used You should cast your id types if you know them for better performance */ extern inline BOOL isEmpty(const char* s) { return !strlen(s); } extern inline BOOL isEmpty(const long x) { return !x; } extern inline BOOL isEmpty(const int x) { return !x; } extern inline BOOL isEmpty(const short x) { return !x; } extern inline BOOL isEmpty(const char x) { return !x; } extern inline BOOL isEmpty(const double x) { return !x; } extern inline BOOL isEmpty(const float x) { return !x; } extern inline BOOL isEmpty(const NSNumber* o) { return !o.boolValue; } extern inline BOOL isEmpty(const NSString* o) { return !o.length; } extern inline BOOL isEmpty(const NSData* o) { return !o.length; } extern inline BOOL isEmpty(const NSSet* o) { return !o.count; } extern inline BOOL isEmpty(const NSArray* o) { return !o.count; } extern inline BOOL isEmpty(const NSDictionary* o) { return !o.count; } extern inline BOOL isEmpty(const NSNull* o) { return YES; } extern inline BOOL isEmpty(const id o) { return o == nil || o == [NSNull null] || ([o respondsToSelector:@selector(boolValue)] && ![o boolValue]) || ([o respondsToSelector:@selector(length)] && ![o length]) || ([o respondsToSelector:@selector(count)] && ![o count]); }
Here’s how the macro is used:
NSLog(@"%@", OR(nil,@"", @0, @{@"foo": @"bar"}, @[])); // outputs {foo = bar;} NSLog(@"%@", OR(nil,@"", @0, @{}, @[])); // outputs () NSLog(@"%@", OR(nil,@"", @0, @[], @"", @{})); // outputs {} NSLog(@"%@", OR(nil,@"", @[], @"", nil)); // outputs (null) NSLog(@"%@", OR(nil,@"", @[], @"", [NSNull null])); // outputs <null> NSLog(@"%@", OR(nil,[NSNull null], @[], @"foo", @"", @"bar")); // outputs "foo" NSLog(@"%i", OR(0, 7, 9, 2)); // outputs 7 NSLog(@"%f", (float) OR(0, 0.0, 9, 2)); // outputs 9.0 int x = 10; NSLog(@"%f", (float) OR(false, x, 5, 2)); // outputs 10.0 NSLog(@"%s", OR("", "foo", "bar")); // outputs foo
As usual all the code above is public domain and free to use.
Boost is required for the macro.