Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On Mac, error: no matching function for call to R_lsInternal #1148

Closed
wch opened this issue Apr 2, 2021 · 10 comments
Closed

On Mac, error: no matching function for call to R_lsInternal #1148

wch opened this issue Apr 2, 2021 · 10 comments

Comments

@wch
Copy link

wch commented Apr 2, 2021

This is related to #897. It took me a while to figure out exactly why this was happening when building the dev version of {httpuv} with the dev version of {later}, but I finally got a minimal reproducible example.

To start off with, this compiles without error:

library(Rcpp)
sourceCpp(code = '
#define R_NO_REMAP
#include <Rinternals.h>
#include <Rcpp.h>
')

However, including `<mach/boolean.h>' results in an error:

sourceCpp(code = '
#define R_NO_REMAP
#include <Rinternals.h>
#include <mach/boolean.h>
#include <Rcpp.h>
')
In file included from file43a8740f39ca.cpp:5:
In file included from /Users/winston/R/4.0/Rcpp/include/Rcpp.h:38:
/Users/winston/R/4.0/Rcpp/include/Rcpp/Environment.h:91:24: error: no matching function for call to 'R_lsInternal'
                return R_lsInternal( env, all ? TRUE : FALSE ) ;
                       ^~~~~~~~~~~~

The error happens because mach/boolean.h has the following:

#ifndef TRUE
#define TRUE    1
#endif  /* TRUE */

#ifndef FALSE
#define FALSE   0
#endif  /* FALSE */

So this results in the same error:

sourceCpp(code = '
#define R_NO_REMAP
#include <Rinternals.h>

#ifndef TRUE
#define TRUE    1
#endif  /* TRUE */
#ifndef FALSE
#define FALSE   0
#endif  /* FALSE */

#include <Rcpp.h>
')

I was able to work around the problem by reordering the includes. Any order of those included header files will compile except the one in the failing example above. (Note that in my case, I didn't include mach/boolean.h directly -- it was included by a file, which was included by a file, etc.)

@eddelbuettel
Copy link
Member

Uggh. Do you think you can test / devise a workaround for the includes we could include?

wch added a commit to rstudio/httpuv that referenced this issue Apr 2, 2021
@wch
Copy link
Author

wch commented Apr 2, 2021

I think the problem that this line defines an enum with TRUE and FALSE values. https://github.com/wch/r-source/blob/trunk/src/include/R_ext/Boolean.h#L35 :

typedef enum { FALSE = 0, TRUE /*, MAYBE */ } Rboolean;

But the lines in <mach/boolean.h> checks if TRUE and FALSE are #define-d, which they aren't, so any TRUE and FALSE tokens in the code simply get replaced with 1 and 0, and are no longer Rbooleans.

#ifndef TRUE
#define TRUE    1
#endif  /* TRUE */

#ifndef FALSE
#define FALSE   0
#endif  /* FALSE */

Here's a possible workaround, though there may well be a better way. The idea is that you'd undefine TRUE and FALSE for the duration of the Rcpp code, and then (if necessary) re-define them at the end.

At the top of the Rcpp code, you have something like:

#ifdef TRUE
#define BEFORE_RCPP_TRUE TRUE
#undef TRUE
#endif

#ifdef FALSE
#define BEFORE_RCPP_FALSE FALSE
#undef FALSE
#endif

And then at the end of all the Rcpp code:

#ifdef BEFORE_RCPP_TRUE
#define TRUE BEFORE_RCPP_TRUE
#undef BEFORE_RCPP_TRUE
#endif

#ifdef BEFORE_RCPP_FALSE
#define FALSE BEFORE_RCPP_FALSE
#undef BEFORE_RCPP_FALSE
#endif

This does compile without error:

sourceCpp(code = '
#define R_NO_REMAP
#include <Rinternals.h>
#include <mach/Boolean.h>

#ifdef TRUE
#define BEFORE_RCPP_TRUE TRUE
#undef TRUE
#endif

#ifdef FALSE
#define BEFORE_RCPP_FALSE FALSE
#undef FALSE
#endif

#include <Rcpp.h>

#ifdef BEFORE_RCPP_TRUE
#define TRUE BEFORE_RCPP_TRUE
#undef BEFORE_RCPP_TRUE
#endif

#ifdef BEFORE_RCPP_FALSE
#define FALSE BEFORE_RCPP_FALSE
#undef BEFORE_RCPP_FALSE
#endif
')

@eddelbuettel
Copy link
Member

Let's revisit when I am more awake. We can hopefully protect a few more use cases that way.

"include order bugs are really gnarly" --me

@kevinushey
Copy link
Contributor

Can we just avoid using TRUE and FALSE altogether define our own tools? E.g.

#define RCPP_FALSE (static_cast<Rboolean>(0))
#define RCPP_TRUE  (static_cast<Rboolean>(1))

And then do some kind of smart search and replace as appropriate.

@kevinushey
Copy link
Contributor

$ rg -l 'TRUE|FALSE' $(find . -name '*.cpp' -or -name '*.hpp')
./src/rcpp_init.cpp
./src/attributes.cpp
./src/barrier.cpp
./src/api.cpp
./src/date.cpp
./inst/examples/OpenMP/piWithInterrupts.cpp
./inst/tinytest/cpp/stack.cpp
./inst/tinytest/testRcppInterfaceExporter/src/RcppExports.cpp

Looks like there aren't too many usages, so shouldn't be too bad to update.

@wch
Copy link
Author

wch commented Apr 2, 2021

@kevinushey I don't think that it'll work to define RCPP_TRUE. The problem is that some functions from R are called, which expect an Rboolean enum value:

return R_lsInternal( env, all ? TRUE : FALSE ) ;

The value passed to R_lsInternal is supposed to be Rboolean:
https://github.com/wch/r-source/blob/a1425adea54bcc98eef86081522b5dbb3e149cdc/src/include/Rinternals.h#L1172

So it needs to be given the TRUE/FALSE Rboolean value -- nothing else will work. The trouble is that the #define TRUE 1 does a text replacement so that the TRUE symbols in the code are replaced with 1, which is not an Rboolean, and hence the error. At least, that's my understanding of things. (I figured some of this out by calling clang++ with the -E flag so that it only used the preprocessor and didn't actually compile the code.)

@Enchufa2
Copy link
Member

Enchufa2 commented Apr 2, 2021

I'm afraid @wch's solution won't work, because it's simply not possible to define a macro to another's expanded result. So this:

#define BEFORE_RCPP_TRUE TRUE

sets BEFORE_RCPP_TRUE to TRUE, literally, not the expansion of TRUE. And the same happens when trying to restore it.

There's a pragma for this, but I'm not sure about its general availability or whether we can use it without complaints from CRAN.

@eddelbuettel
Copy link
Member

eddelbuettel commented Apr 2, 2021

Making the Devil's Advocate argument: Is this too rare to implement something?

Luckily and thanks to his persistence, @wch has worked out how to address it for httpuv so maybe we just document this in the Rcpp FAQ vignette? Is it really needed everywhere? Otherwise it is of course 'cheap' to add a pair of headers....

Lastly, given that we include Rinternals.h for our use and the use by others, and also define R_NO_REMAP, what about this variant of @wch's "doesn't build" code:

sourceCpp(code = '
#include <mach/boolean.h>
#include <Rcpp.h>
')

Will that work? I don't have mach/boolean.h so I can't test ....

@wch
Copy link
Author

wch commented Apr 2, 2021

@Enchufa2 Thanks for pointing that out.

@eddelbuettel The example you provided does compile. I think it's because this is what happens:

  • mach/boolean.h does #define TRUE 1.
  • Rcpp.h includes Rinternals.h, which includes R_ext/Boolean.h. This file does #undef TRUE and then creates the Rboolean enum, which has TRUE and FALSE.

The end state is that the TRUE macro is not defined. The TRUE symbol represents a value from the Rboolean enum.

In contrast, this is what happens in the error examples:

  • The test program includes Rinternals.h, which includes R_ext/Boolean.h. This creates the Rboolean enum, which has TRUE and FALSE.
  • mach/boolean.h does #define TRUE 1.
  • Rcpp.h includes Rinternals.h, but the content gets skipped because it has already been included.

The end state here is that all instances of TRUE in the code get replaced with 1 by the preprocessor.

As for whether it's worth fixing in Rcpp, I don't really have any strong opinions either way. It apparently is a very rare case.

@eddelbuettel
Copy link
Member

I put a short extension to the FAQ in: 1684878

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants