Quantcast
Channel: XibXor » variadic
Viewing all articles
Browse latest Browse all 2

Non-Coalescing OR operator for Objective C

$
0
0

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 cannot take run-time values, only compile-time consts. The only way to receive non-const values would be in the function part of the template, which would kill short-circuiting.

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.

Download XXOR.h here


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles



Latest Images