Reactivecocoa: [RACSignal combineLatest:reduce:] won't compile as Objective-C++

Created on 12 Nov 2013  ·  17Comments  ·  Source: ReactiveCocoa/ReactiveCocoa

If I have my implementation file type set to "Objective-C++ Source" I get a compiler error like

Cannot initialize a parameter of type 'id (^)()' with an rvalue of type 'id (^)(NSNumber *__strong, NSNumber *__strong)'

when attempting to compile something like

RAC(self.submitButton, enabled) = [RACSignal 
  combineLatest:@[siteAddressValidSignal, accessKeyValidSignal] 
  reduce:^id(NSNumber *siteAddressValid, NSNumber *accessKeyValid){
    return @(siteAddressValid.boolValue && accessKeyValid.boolValue);
}];

If I change the file type to Objective-C, it compiles fine.

bug

Most helpful comment

Thanks for providing the sample project, it helped me a lot.

Turns out the easiest way to deal with this issue is to cast the block to id, sidestepping the type checking entirely:

RAC(self.submitButton, enabled) = [RACSignal
    combineLatest:@[siteAddressValidSignal, accessKeyValidSignal]
    reduce:(id)^id(NSNumber *siteAddressValid, NSNumber *accessKeyValid){
        return @(siteAddressValid.boolValue && accessKeyValid.boolValue);
    }];

The code compiled without issues once I reverted the changes to RAC and the sample program seems to work fine.

All 17 comments

Can you try changing the declaration of the method to + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)(...))reduceBlock; and see if it works then?
You shouldn't need to recompile RAC, just edit the header.

That causes another compiler error

ISO C requires a named argument before '...'

Right. How about + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)(id firstValue, ...))reduceBlock;?

Sorry it took me a while to find the old issue where this was done (#630, PR #636).

According to the block language specification (http://clang.llvm.org/docs/BlockLanguageSpec.html#block-variable-declarations)

A Block that takes no arguments must specify void in the argument list [voidarg.c]. An empty parameter list does not represent, as K&R provide, an unspecified argument list. Note: both gcc and clang support K&R style as a convenience.

which means the syntax we're using, while supported, is not actually valid. That's probably why it doesn't work with ObjC++.

I hope the declaration in my previous reply works, it would suck to have to go back to having id instead of a block type.

I'm using ReactiveCocoa as a subproject & target dependency in an iOS project, so it seems to recompile ReactiveCocoa no matter what. I tried compiling RAC unchanged, then removing it from my target dependencies so it would not recompile when I build my project, then changing the header signatures. Xcode insisted on the implementation matching, so I changed the implementations too. (Had to change -reduceEach: and some others as well.)

But then it still refuses to compile my code, with the same Cannot initialize a parameter of type 'id (^)()' with an rvalue of type 'id (^)(NSNumber *__strong, NSNumber *__strong)' as before, as if I haven't changed the header signature.

Would this work better if I was using RAC as a CocoaPod?

Are you sure it's getting the updated header and not an old one from the build products directory? It should at least give a different error like when you tried with just the ....

Alternatively can you create an empty project with your settings which also exhibits the problem and upload it?

I did clean the output folder repeatedly, both through Xcode (⌥⇧⌘K) and then through Finder, restarted Xcode a few times... you know, the usual voodoo.

Here's a sample project though. Build the ObjCppReduceError target for iPhone Retina 4-inch iOS 7 Simulator. ViewController.m is the Objective-C++ file containing the offending call.

In the sample I changed the signature for -combineLatest:reduce:, but I still eventually get an error in my Objective-C++ view controller. I say "eventually" because sometimes the error doesn't show up until a few seconds after the build supposedly completes. (Maybe when it's still indexing after I nuked the build dir?)

I tried to modify -reduceEach: in a similar fashion, but that caused a bunch of incompatible block pointer type errors like

Incompatible block pointer types sending 'RACSignal *(^)(__strong id, NSDictionary *__strong)' to parameter of type 'id (^)(__strong id, ...)'

I checked that project out on a new machine, and it didn't show the error in ViewController.m at first, but then Xcode did that thing where it adds new errors a few seconds after it finishes the build.

Thanks for your help on this. I'd rather use plain Objective-C, but alas I have tons of existing shared cpp code to deal with, so it's tricky. The file in my actual project seems to work ok as an Objective-C file for now, as I haven't started importing any of my usual shared C++ baggage. But I fear this will eventually be a big deterrent to me using ReactiveCocoa unless I rearchitect my whole project.

Thanks for providing the sample project, it helped me a lot.

Turns out the easiest way to deal with this issue is to cast the block to id, sidestepping the type checking entirely:

RAC(self.submitButton, enabled) = [RACSignal
    combineLatest:@[siteAddressValidSignal, accessKeyValidSignal]
    reduce:(id)^id(NSNumber *siteAddressValid, NSNumber *accessKeyValid){
        return @(siteAddressValid.boolValue && accessKeyValid.boolValue);
    }];

The code compiled without issues once I reverted the changes to RAC and the sample program seems to work fine.

Sweet! That seems to do the trick in the real project too.

Do you think this is a case where clang's Objective-C++ support hasn't yet implemented all the niceties it has for Objective-C, or should that be a question / radar for Apple?

Thanks for the help.

It could be that supporting K&R style unspecified parameters breaks C++ in some way, I'm not familiar enough with C++ to know why that would be the case, but I do know C++ is not a strict superset of C, unlike Objective-C, which means there can be valid C code that isn't valid in C++.

As I mentioned before we're abusing of an implementation detail of clang to have nice block types instead of passing id around, so it's not really something you can blame Apple for not supporting.

Anyway I'm glad this solves the problem. I'll close the issue now but feel free to follow up or open new ones if you encounter any other troubles.

Objective-C++ is not very common, so we appreciate any feedback on the subject.

I don't think this is just an Objective-C++ problem; I experience the same build error. @Coneko's solution of casting to id sidestepped the error, but I'm compiling Objective-C code, so I'm puzzled why this works for everyone else.

I'm using ReactiveCocoa 2.3, Xcode version 5.1 (5B130a), and my project has "Compiler for C/C++/Objective-C" set to "Default compiler (Apple LLVM 5.1)". Here's the specific version:

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix

Let me know if there's any other information I can provide you with. If you're interested in diagnosing the problem, I can upload a sample project as well.

Hi @modocache, I'll look at your sample project.

Thanks, @kastiglione! Here's a minimal repro: https://github.com/modocache/ReactiveBrokeo

As you can see, it fails to build on Travis CI as well.

@modocache I checked that repo, and the problem of your code is reduceBlock for -reduceEach: returns nothing, so return type of the block is inferred as void. But because return type of reduceBlock should be id, it leads to build error.

So following will be OK:

reduceEach:^(NSNumber *yes, NSNumber *no) {
        NSLog(@"Am I indecisive? Well, %@ and %@.", yes, no);
        return @"foobar"; // The return type would be inferred as `NSString *` (it is compatible with `id`).
}

If you'd like to return nil, then:

reduceEach:^id (NSNumber *yes, NSNumber *no) {
        NSLog(@"Am I indecisive? Well, %@ and %@.", yes, no);
        return nil; // This would be inferred as `void *`, so you have to specify return type as `id`.
}

@ikesyo You're absolutely right! This pull request demonstrates the fix: https://github.com/modocache/ReactiveBrokeo/pull/2

Thanks for the insight! That resolves my issue, sorry for taking up your time, @kastiglione.

No problem! :metal:

:cake:

Was this page helpful?
0 / 5 - 0 ratings