I am checking the length of an Rcpp::List
object, and notice that I get different results on Windows than on Mac.
Here is my C++ code, in the file "test.cpp"
. I have a Data
class, whose constructor takes an Rcpp::List
and extracts its element named "constraints"
. Next, I have a function foo
which constructs a Data
element, extracts the length of constraints
, and returns it.
#include <Rcpp.h>
using namespace Rcpp;
struct Data {
Data(List data) :
constraints { as<List>(data["constraints"]) } {}
~Data() = default;
const List constraints;
};
// [[Rcpp::export]]
int foo(List dat) {
Data dd {dat};
int result = dd.constraints.length();
return result;
}
Here is my session:
> sessionInfo()
R version 4.3.2 RC (2023-10-30 r85440)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.1.2
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Europe/Oslo
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
loaded via a namespace (and not attached):
[1] vctrs_0.6.4 cli_3.6.1 knitr_1.45 rlang_1.1.2 xfun_0.41 processx_3.8.2
[7] purrr_1.0.2 styler_1.10.2 glue_1.6.2 clipr_0.8.0 htmltools_0.5.7 ps_1.7.5
[13] fansi_1.0.5 rmarkdown_2.25 R.cache_0.16.0 evaluate_0.23 tibble_3.2.1 fastmap_1.1.1
[19] yaml_2.3.7 lifecycle_1.0.4 compiler_4.3.2 fs_1.6.3 Rcpp_1.0.11.2 pkgconfig_2.0.3
[25] rstudioapi_0.15.0 R.oo_1.25.0 R.utils_2.12.3 digest_0.6.33 R6_2.5.1 reprex_2.0.2
[31] utf8_1.2.4 pillar_1.9.0 callr_3.7.3 magrittr_2.0.3 R.methodsS3_1.8.2 tools_4.3.2
[37] withr_2.5.2
Next, I source the C++ code:
Rcpp::sourceCpp("test.cpp")
Giving foo
a list whose constraints
element is NULL
, gives length zero as expected:
> data <- list(a = rnorm(100), constraints = NULL, b = letters)
> foo(data)
[1] 0
Next, giving it a list whose constraints
element has 26 letters, returns length 26:
> data <- list(a = rnorm(100), constraints = letters, b = letters)
> foo(data)
[1] 26
I run the same R code on Windows, but obtain different results.
Here is my session info:
> sessionInfo()
R version 4.3.2 (2023-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)
Matrix products: default
locale:
[1] C
time zone: Europe/Oslo
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
loaded via a namespace (and not attached):
[1] compiler_4.3.2 magrittr_2.0.3 R6_2.5.1
[4] cli_3.6.1 tools_4.3.2 RcppArmadillo_0.12.6.6.0
[7] rstudioapi_0.15.0 Rcpp_1.0.11.6 brio_1.1.3
[10] testthat_3.2.0 rlang_1.1.2
I source the script again:
> Rcpp::sourceCpp("test.cpp")
Here is the result when constraints
is NULL
. Surprisingly, it returns length 1.
> data <- list(a = rnorm(100), constraints = NULL, b = letters)
> foo(data)
[1] 1
Here is the result when constraints
has 26 letters. Again it returns length 1.
> data <- list(a = rnorm(100), constraints = letters, b = letters)
> foo(data)
[1] 1
Is this expected? Am I doing something here which causes undefined behavior, and hence gives different results with different compilers/platforms?
How should I change the code to get the behavoir I want, which is that the length should be zero when it is NULL
and nonzero otherwise?
Based on Dirk Eddelbuettel's answer below, I realize that it's best to refactor my code such that it does not contain such a deep level of nesting. In any case, I think it's worthwhile to include here that Eddelbuettel's test returns the same on Windows as on Mac:
> Rcpp::cppFunction("int ll(List L) { return L.length(); }")
> ll(NULL)
[1] 0
> ll(letters)
[1] 26
I suspect this code:
constraints { as<List>(data["constraints"]) }
on Linux initializes constraints
directly with as<List>(data["constraints"])
, and on Windows initializes it as a list containing one item, which is as<List>(data["constraints"])
.
This {}
initialization syntax is normally used to initialize with a list of things, via std::initializer_list
. It might not be doing that on Linux, due to a slightly different version of R, the compiler, or the compiler options.
Change {
and }
to (
and )
in this part, to force it to initialize constraints
directly with as<List>(data["constraints"])
.