The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

03 - Package Skeleton

The reference for this vignette is Wickham 2024.

1 Loading the Required R Packages

This vignette and the next use the following R packages:

library(cpp4r)
library(tinydev)

2 Creating a Dummy Package

You can create a new package in RStudio (or VSCode) by running:

pkg_template("~/mypkg")

This command will create a new folder containing the mypkg package.

3 Print a Number

The pkg_template() function automatically creates ./mypkg/R/mypkg-package.R with the following content:

#' @title Package Title
#' @description Some description. CRAN asks for at least one paragraph with two full sentences. Like this.
#' @useDynLib mypkg, .registration = TRUE
"_PACKAGE"

It also creates ./mypkg/src/main.cpp. The rest of src/ aims to provide an organization to keep the code organized into different files according to their purpose.

Give a look at ./mypkg/src/01_plus_one.h, which contains the following C++ code:

/* roxygen
@title Plus 1 (C++)
@param x integer
@description It adds 1 to an integer value.
@export
@examples plus_one(1)
*/
[[cpp4r::register]]
int plus_one(int x) {
  return x + 1;
}

This code is called by main.cpp and it makes sense to keep this organization when the codebase grows, otherwise you can end up with a single large cpp file containing thousands of lines of code that will be harder to maintain. With R packages, there is no need to create a main() function in C++, which is the entry point of a standalone C++ program.

Unlike cpp11, cpp4r can document the C++ functions using Roxygen comments. The comments must be placed before the [[cpp4r::register]] attribute.

4 Exporting a Registered Function

Decorating a function with [[cpp4r::register]] only makes it callable via .Call() from R; it does not export or document it. For example, this alone leaves sum undocumented and unexported:

[[cpp4r::register]] int sum(int a, int b) { return a + b; }

There are two ways to export a registered function:

Either way, register() and [[cpp4r::register]] alone never modify NAMESPACE; running tinydev::pkg_document(".") (or hand-editing NAMESPACE) is what actually exports the function.

With cpp11, you would need to write a separate wrapper, like this:

[[cpp11::register]] int plus_one_(int x) {
  return x + 1;
}
#' @title Plus 1 (C++)
#' @param x integer
#' @description It adds 1 to an integer value.
#' @export
#' @examples plus_one(1)
plus_one <- function(x) {
  plus_one_(x)
}

With cpp4r you can type:

/* roxygen
@title Plus 1 (C++)
@param x integer
@description It adds 1 to an integer value.
@export
@examples plus_one(1)
*/
[[cpp4r::register]] int plus_one(int x) {
  return x + 1;
}

The R version of the previous function is as follows:

#' Plus one (R)
#' @param x integer
#' @description It adds 1 to an integer value.
#' @export
#' @examples plus_one_r(1)
plus_one_r <- function(x) {
  x + 1
}

To use the C++ function, you can register and document it by running:

cpp4r::register()
tinydev::pkg_document(".")
tinydev::pkg_load(".")

Which should print:

> plus_one(1)
[1] 2

Try plus_two(1) and plus_two(1.0). Why does 1 and 1.0 matter in C++?

5 Number’s Sign

A more complex function is one that returns the sign of a number:

[[cpp4r::register]] int sign_cpp(double x) {
  if (x > 0) {
    return 1;
  } else if (x == 0) {
    return 0;
  } else {
    return -1;
  }
}

Add this function to a new file src/03_sign.h.

Here is the R version of the function:

sign_r <- function(x) {
  if (x > 0) {
    1
  } else if (x == 0) {
    0
  } else {
    -1
  }
}

Do not forget to include the new header file in main.cpp.

Document both functions and compare their outputs.

6 Installing the Package

To document and install the package as an R library, you can run the following functions:

cpp4r::register()
tinydev::pkg_document(".")
tinydev::pkg_install(".")

Afterward, you can access the functions by loading the package with library(mypkg).

Each time you need to make changes to the C++ code, you can run load_all() again to test and then reinstall the package.

7 Good Practice

It is good practice to include a license for your code. For example, you can use the Apache license by running:

tinydev::license_apache(".")

You also need to ignore files that are unnecessary for package installation. For example, to ignore the docs folder, add ^docs$ to .Rbuildignore. Your .Rbuildignore file can include the following lines, which you can edit manually to exclude specific files and directories:

^\.vscode$
^LICENSE\.txt$
`^docs$`

8 References

Wickham H, Bryan J, Barrett M, Teucher A (2024). “Automate Package and Project Setup.” https://usethis.r-lib.org/.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.