7 Rcpp

7.1 core idea

only use Rcpp as a tool to pass object:

  1. write your code logical in C++ way, use STL as much as possible
  2. only use Rcpp function for very simple operation, like .size(), [i]

7.2 complier options

There are two files:

  • ~/.R/Makevars specifies how you build source package and standalone C++ file
  • src/Makevars specifies how your package is built by others

Typically, you can use ~/.R/Makevars to switch which complier to use 14:

CC=clang
CXX17 = clang++

while C++ version (like C++17) should be specified per .cpp file using // [[Rcpp::plugins(cpp17)]], since you can’t determine which version others’ code conform to.

7.3 internals

  1. IntegerVector 等都可以看作是 shadow copy,记住不能 move

7.4 current limit

Rcpp amis to bring high-performance computering into R, however, I desire many C++ native feature thus 碰了很多次壁。

  1. pure C++ function can’t be exported. Since you can only export R version or R & C++ version, but functions like void foo(std::list<int> bar) can’t convert to R version.

  2. exporting a header file which depends on a header file from another package is impossible ^[for paristools, the final solution is to give up exporting anything in header file. Since we find we needn’t export functions like as_locs() at all, we needn’t export the defination of custom struct.

    in a standalone file

    // [[Rcpp::depends(paristools)]]
    #include <paristools.h>

    would cause

    In file included from /home/zhuoer/.local/lib/R/paristools/include/paristools.h:7:
    /home/zhuoer/.local/lib/R/paristools/include/paristools.hpp:4:10: fatal error: 'Rcppzhuoer.h' file not found
    #include <Rcppzhuoer.h>
             ^~~~~~~~~~~~~~

    That’s because although paristools depends on Rcppzhuoer, Rcpp::depends() only adds the former to include path.

    The situation is better if you use <paristools.h> in a package, since LinkingTo is recursive

  3. testthat can’t test C++ export

    Rcpp use find.package() to determine package path, when you use testthat find.package('pkg') return the project dir (in normal session, it return package in the library, .local/lib/R/pkg). The reason is that find.package() looks for loaded namesace first, and testthat load pkg namespace from the the project dir. [^debug-testthat-cpp-export]

    A work-around is to use callr::r().

7.4.1 坑边杂谈

以下为作死记录,可能会移到 blog

  1. 标准库和编译器对右值的优化根本轮不到我来操心

    刚开始写 Rcpp 时,我很在意效率,甚至还返回右值引用等,结果还招致了 complier warning,查了半天资料还去不掉。最后我放弃了,改为写了一个 struct, Rcppzhuoer::foo, 其移动构造、复制都会打印一条消息,用来检测有没有浪费。比如我要用到如下的自定义类型,就把 Rcppzhuoer::foo 作为一个成员。结果根本没有一点浪费,只需要在必要的时候加上 std::move() 就好了

    struct bar {
        std::string messsage {};
        int count {};
        Rcppzhuoer::foo<void> foo1 {}; // comment me in release
    };
  2. 想用新标准,还是 std 大法好

    之前启用 C++17 时,我曾用 grep "std=" /usr/lib/R/etc/Makeconf 来搜索 R 定义的 alias,然后把 PKG_CXXFLAGS = $(CXX17STD) 添加到 src/Makevars 文件。但是 R 手册提到

    It should be written if at all possible in a portable style, in particular (except for Makevars.win) without the use of GNU extensions.

    CXX17STD = -std=gnu++17,所以我就改为直接用 PKG_CXXFLAGS = -std=c++17

  3. Rcpp::plugins() 没毛病

    之前用不了 clang++,我翻出了 // [[Rcpp::plugins(cpp17)]] 的源代码

    .plugins[["cpp17"]] <- function() {
        if (getRversion() >= "3.4")         # with recent R versions, R can decide
            list(env = list(USE_CXX17 = "yes"))
        else
            list(env = list(PKG_CXXFLAGS ="-std=c++17"))
    }

    当时我觉得是 USE_CXX17 = "yes" 导致 Rcpp 只能使用 g++,还修改了 Rcpp 的源代码。后来看来,应该是我弄错了。这里把我翻到的代码贴出来,以便以后查阅。

  4. debug why testthat can’t test C++ export

    Thanks for verbose = T, I find the code doesn’t include #include <paristools.h> (later I find Rcpp can’t find the file, so it doesn’t include). After set includes parameter, still fail.

    Thanks for cacheDir = 'tests/testthat', I can get the temp .cpp file, and directly run clang++ command. Finally, I found the cause.


  1. Formerly I use

    CC=clang
    CXX   = clang++
    CXX98 = clang++
    CXX11 = clang++
    CXX14 = clang++
    CXX17 = clang++
    CXX1X = clang++

    But later I found some packages is only compatible with g++ (such as DropletUtils 1.2.2), so I choose to only use clang++ for C++17, assuming that those bad packages won’t use latest standard (while myself always do that)↩︎