Changelog
Source:NEWS.md
cppally (development version)
Breaking changes
Sequences no longer abort on overflow, but instead silently return
NA.Removed
r_vec::subsetandr_factors::subsetas they didn’t thematically fit in with the rest of the members. The freesubsetfunction is still available.Renamed
common_lengthtolist_common_length.common_lengthnow accepts variadic inputs.Relocated various headers to and from the sugar folder.
r_factorsmember functionsget_codes,set_codes, andnew_codeshave been removed. The functionality of these can be achieved with the existing members.r_sexp::lengthhas been fully deprecated and removed. Usecppally::lengthfor returning object length.as<r_str>(r_dbl::inf())now returns title caser_str("Inf")instead of lower caser_str("inf"). Similarlyas<r_str>( -r_dbl::inf() )returnsr_str("-Inf").C++ char types (except
const char*andunsigned char) no longer satisfyCastableToRScalarand therefore cannot participate in R/C++ function registration.Many
r_sexp-based free functions have been removed and there are a few reasons for doing so. The first being that cppally didn’t provide a complete set ofr_sexpfree functions anyway, and so providing an incomplete set felt lackluster. Another reason is that removing these methods speeds up compilation and makes the API more lightweight and predictable. Users are now encouraged to write their ownr_sexpmethods as and when they require them.
Bug fixes
Fixed a bug where matching on integer vectors with a non
NAnomatchvalue would still returnNA.Fixed a bug where getting
r_dfrows would return CHARSXP instead of STRSXP.Fixed a bug where
r_vec::applywould throw a compiler error when called on lists and character vectors.Fixed a bug where the wrong OMP pragma was being used for non-SIMD parallel for loops.
Fixed a bug where calling
set_attr()onr_dforr_factorswould produce a compiler error.
r_function
New class
r_functionto safely call R functions from C++.New helper
pkg_envto return the environment of a package, allowing users to easily retrieve functions from specific packages, e.g.r_function("foo", pkg_env<"bar">()).
random_stream
A new class
random_stream, allowing one to use C++ random number generators, while preserving reproducibility from R’s seed. This means that numbers drawn fromrandom_streamare reproducible viaset.seed().random_streamis driven by xoshiro256++ (Blackman & Vigna) via the bundled Xoshiro-cpp library (Ryo Suzuki, MIT), rather than a<random>engine. It still modelsstd::uniform_random_bit_generator, so it can be handed to<random>distributions and algorithms such asstd::shuffle.Default-constructing a
random_streamseeds it from R’s RNG (via twounif_rand()draws), soset.seed()still determines every draw, while R’s RNG state is only touched once per stream rather than once per draw.random_streamcan also be constructed directly from a customuint64_tseed, allowing the generation of random numbers without affecting R’s RNG state. This can be useful if you do not need reproducibility from R’sset.seed().random_stream::split()returns an independent child stream. Parallel code should build its streams up front viasplit()before dispatching work.random_stream::indexsamples random indices in [a, b], allowing for fast sampling with replacement. It uses Lemire’s divisionless method (source: arXiv:1805.10941) along with a portable 128-bit multiply, provided by the ankerl library. This makes it fast and platform-agnostic, therefore results should be reproducible across platforms.draw_from_rmust be used if one wishes to call scalar RNG functions from the R C API header ‘Random.h’.
r_date & r_psxct
A new complete set of fast and parallelisable thread-safe member and free functions for r_date and r_psxct, which respectively represent R dates and date-times. Currently, all r_date and r_psxct member functions are thread-safe except for r_date::date_str() and r_psxct::datetime_str().
New field accessors like
year(),month(),week(),day(),hour(),minute(),second(),iso_week()andiso_year().New date arithmetic member template function
add<>, allowing flexible addition with time units such as “years”, “months”, “weeks”, “days”, “hours”, “minutes”, and “seconds”.New date rounding member functions
floor,ceilingandround.New static member functions
r_date::today()andr_psxct::now()to get the current date or current time.r_psxct::now()returns the current time in fractional seconds to microsecond precision.New template function
time_diff<>to calculate time differences between two dates or date-times.
Date and date-time arithmetic
To perform time arithmetic with dates and date-times, use add<>, e.g. x.add<"days">(1) adds 1 day to x, and x.add<"months">(-3) subtracts 3 months from x.
The on_impossible_date argument of add<> allows you to control how to handle impossible dates that arise when performing month-based arithmetic, e.g. 31 Jan + 1 month = ?
All roll options:
enum roll : uint8_t {
none = 0, // does not roll and impossible dates are returned as NA
backward = 1, // rolls backwards until the last day of the current month is reached
forward = 2, // rolls to first day of next month
away = 3, // rolls forward when adding and backward when subtracting
nearest = 4 // rolls backward when adding and forward when subtracting
};Sub-day units are accepted in both r_date and r_psxct arithmetic. For r_date specifically, fractional dates are produced when using sub-day units (such as “hours”) for date arithmetic. r_date::today().add<"hours">(12) will produce a fractional date representing noon of the current day and r_date::today().as_datetime().add<"hours">(12) will produce a date-time r_psxct also representing noon of the current day.
I would generally recommend against using sub-day units with r_date and to use r_psxct if you ever need sub-day granularity.
To add sub-second units such as milliseconds and microseconds, simply supply fractional seconds, e.g. x.add<"seconds">(1.0/1000.0) adds 1 millisecond to x. It’s worth noting that loss in floating-point precision can occur when working with small microseconds, though this is inherent in R’s POSIXct class as well.
Date and date-time rounding
To round dates and date-times, use member functions floor, ceiling and round.
For example, x.floor<"months">() floors x to the start of the current month. x.ceiling<"months">() advances x to the start of the next month, unless it is already at the start of the month.
Rounding functions accept a week_start argument, which is only relevant for rounding “weeks”. It follows the same convention as wday(), where 1 is Monday and 7 is Sunday (the default). For example, x.floor<"weeks">() floors x to the most recent past Sunday, whereas x.floor<"weeks">(1) floors x to the most recent past Monday.
When the date is exactly at the midpoint between boundaries, round() will floor the date, so for example if we are rounding a date-time to the nearest day and the time is at noon, the date-time gets floored to midnight on the same day.
Date and date-time differences
To calculate the time difference between two dates or date-times, use time_diff<>.
For example, time_diff<"months">(x, y) calculates the difference in months between x and y. If x and y do not share the same day-of-the-month, then a fractional difference is returned. Use cppally::floor() to floor the result if you always need a whole difference.
To return time differences using n time unit periods (the default is 1), just pass it to the n argument, e.g. time_diff<"months">(x, y, /*n=*/ 3) returns the difference in quarters between x and y, and similarly time_diff<"days">(x, y, /*n=*/ 7) is the same as time_diff<"weeks">(x, y).
For monthly time differences, sometimes impossible dates can arise when either x or y has a day-of-the-month that is larger than the other’s number of days in the month. While the default time_diff<"months">() would return NA, we can control the internal impossible date rolling with the on_impossible_date arg.
For example, assuming x is 31 Jan 2024 and y is 29 Feb 2024, time_diff<"months">(x, y, 1, roll::nearest) rolls the impossible 31 Feb date back to 29th February, resulting in a difference of exactly 1 month. If we specified roll::away the date becomes 1st March instead, giving a fractional answer that is slightly less than 1 month.
Improvements
cpp_sourcenow generates OpenMP flags by default, so its compiled functions can use both OpenMP SIMD vectorisation and multi-threaded execution (threads set viaset_threads()).cpp_sourcegains theopenmpargument.Integer multiplication overflow handling is now more portable.
r_symnow constructs symbols from strings (const char*,r_str,r_str_view) under unwind protection, so R errors can no longer longjmp past C++ destructors.Sorting speed has been improved for both character vectors and numeric vectors. Sorting is faster for character vectors when there are a relatively high proportion of unique strings. Sorting is also dramatically faster for double vectors when all values in the vector are exact whole numbers.
groupsclass membersstarts(),counts()now see substantial speed improvements when data are in group-sorted order. When sorted,starts()andcounts()may perform a hybrid linear/exponential (or galloping) search. This new search strategy dramatically improves performance on large data with small numbers of groups. Time complexity now scales with the number of groups and not the size of data.groups::order()also sees speed improvements for unsorted data, due to the fact that it unconditionally applies a counting sort, without checking forNAvalues (since group IDs do not haveNAvalues) or scanning the group IDs for their range, likecppally::order()generally (and correctly) does for other integer vectors. When data are already sorted it simply returns the sequence [0, n - 1] for n data points (as it did before).n_uniquehas been sped-up for integer vectors.Some algorithms now benefit from a cardinality estimate. For large inputs, an extended Chao estimator (doi:10.1016/j.csda.2011.01.017) is applied to a random sample of the data to choose a better initial reserve size for the hash maps involved. This improves performance on high-cardinality data, where it avoids repeatedly resizing the map as new keys are added.
Algorithms that utilise hashing should now see speed improvements for strings in particular, as SEXP type checking is now skipped for hashing specifically.
use_cppally()now includes the “$(CXX_VISIBILITY)” Makevars flag, making dll symbol visibility hidden by default. This should have no effect on code written with cppally, though it may slightly improve compilation time. R/C++ registered functions are now registered with the R C API tagattribute_hidden.r_vec<>gains aninitializer_listconstructor.
Other new features
New function
scalar_coerce. Use this withallow_lossy = trueif you want to returnNAinstead of an error on a completely lossy scalar coercion. For example, converting a letter string to a double (e.g.as<r_dbl>(r_str("a"))) always results in an error by default whereasscalar_coerce<r_dbl>(r_str("a"), /*allow_lossy=*/ true)returnsna<r_dbl>().New
copymember forr_vec,r_factorsandr_df.copyshallow copies the vector by creating a fresh copy of the atomic data, without deep copying lists or attributes, just likeRf_shallow_duplicate.New by-group left-fold functional
reduce_by_group, allowing for very efficient binary reductions by-group.New function
list_recycleto recycle vectors of a list.New
r_sexpvisit helpersvisit_asandview_as.New
r_strmember functionis_utf8to check whether a string has valid UTF-8 encoding, which includes ASCII strings.New
r_strmember functionas_utf8to convert a string to a UTF-8 string. If the string is already a UTF-8 string, it is a no-op and simply returns the samer_str.New class
string_literalto facilitate compile-time string literal NTTP programming.R function
cpp_evalgains a new argument,cppally_header, allowing one to compile expressions using the optional light header “cppally_light.hpp”.r_factorsgains a new member function,refactor, which creates a newr_factorsobject given a new set of levels.New R function
use_openmp()to set OpenMP-enabling Makevars flags.A new optional feature where users can now restrict the set of candidates that participate in template dispatch for template-registered R/C++ functions via
use_template_dispatch_candidates(). For example, if a user wishes to write a C++ algebra library using only integers and doubles, they can calluse_template_dispatch_candidates(c("r_int", "r_dbl")), and any template they register to R will only ever accept at most those types, regardless of the concepts and constraints of the template. This can dramatically reduce compilation times for template-heavy code when specifying a small number of candidates. Generally the same effect can be achieved with concepts and constraints, but this acts as an additional layer to ensure faster compile-times in scenarios where it makes sense to use a small candidate set.
cppally 1.1.0 (2026-07-12)
CRAN release: 2026-07-12
Breaking changes
Improved type-safety regarding implicit coercion of RScalar types. They now can implicitly coerce only to their wrapped types, whereas before they could implicitly coerce to that as well as other C types.
RTimeTypearithmetic has been deprecated and removed.Arithmetic involving
r_lglis now always promoted tor_int, matching R’s own semantics.common_math_t<T, U>now never returnsr_lgl, effectively treatingr_lglasr_int.
Bug fixes
Fixed bug where exclusion of concepts header was affecting vignette creation on macos.
Fixed incorrect NA handling of
r_dateandr_psxct.Fixed a bug where
identical()would returnfalsefor two genuinely identicalNA_real_values.NULLoptional arguments are now correctly handled. Vector, factor and data frame arguments can now beNULLto allow for optional argument programming from R.Fixed a bug where checking exact equality (via
identical) for C/C++ types was not compiling due to template ordering issue.
r_lgl (logical) semantics
r_lglcan now implicitly coerce toint(and onlyint), consistent with the other RScalar types.r_lglvalues are now always normalised on construction internally to either 0, 1, orNA.
Arithmetic
Integer overflow is now explicitly handled in all cppally arithmetic, returning
NAwhen detected. Integer addition and subtraction in particular are written using branchless or vectorisable code, enabling SIMD vectorisation.Integer in-place division
/=now matches R’s%/%(floored division), instead of C’s truncating division, e.g.-7L /= 2Lnow gives-4instead of-3. Division by zero orNAnow correctly returnsNAinstead of crashing the R session. Precision is also preserved for larger_int64values that previously round-tripped throughdouble.Scalar in-place arithmetic that narrows a wider result back to the left-hand type (e.g.
r_int += r_int64) now checks the value fits first, givingNAon overflow instead of a silently wrapped result.%now accepts mixed integer/float operands (e.g.r_int(7) % 2.5), promoting and flooring consistent with R’s%%.Vector in-place arithmetic (
+=,-=,*=,%=) now routes through the scalar operators, fixing a bug where mixed-width operations (e.g.r_vec<r_int> += r_vec<r_int64>) could silently truncate values, including turningNAinto0.
Improvements
Better errors when passing invalid types to template functions.
Constructing invalid dates and date-times now returns
NAinstead of an error.New concept
RNumberto represent number-based types liker_intandr_dbl.New member
is_nafor RScalar types, in addition to the equivalentis_nafree function.New member function
na()for RScalar classes.Relational operators have been extended to cover all RNumericType classes which includes
r_dateandr_psxct.
cppally 1.0.0 (2026-07-02)
CRAN release: 2026-07-02
First major release. cppally’s public API is now considered stable. While there may be structural changes to the r_df and r_raw classes in the future, cppally’s vector and scalar classes are considered stable.
Breaking changes
r_sexp.lengthhas been deprecated, in favour of the free functionlengthlength(r_df)now returns the number of rows instead of the number of cols, marking a shift in how cppally treats data frames. They are now seen as row-wise vectors.Setting attributes on plain
SEXPis now unsupported, e.g. viacppally::attr::set_attr. Use cppally types such asr_vector,r_factors,r_dfand in some casesr_sexpfor attribute manipulation.Various out-of-place or trivial to implement
r_vecmember functions have been removed.visit_vector,visit_sexpandview_sexphave been deprecated in favour of the more flexible constrainedr_sexpvisitors:r_sexp_visit,r_sexp_viewandr_sexp_mutate. These allow concepts and custom constraints to be applied directly on the lambda’s template parameter, e.g.r_sexp_visit(x, [&]<RVector T>(T vec){})— herexis dispatched as its concrete vector type and aborts at runtime if the underlying type isn’t anRVector.r_sexp_viewis the non-owning sibling: the wrapper handed to the lambda is a view (no extra protect), so it must not outlivex.r_sexp_mutateis for in-place mutation: it movesxinto the typed wrapper (making it the sole owner), callsf, then writes the result back.r_factorselements are now treated asr_strin member functions likegetandsetr_sexp_visitnow visitsr_nullasr_vec<r_sexp>(r_null), essentially treatingNULLas an empty list but without changing the underlying data.
For example, in the below pseudo-code, when x is r_null of type r_sexp, r_sexp_visit will disambiguate it as r_vec<r_sexp>(r_null), preserving its data as R’s NULL but assigning its type as r_vec<r_sexp> (list).
This preservation behaviour is not new, in fact all r_vec<T> vectors preserve r_null by design, allowing for efficient and easier attribute manipulation with vectors that may or may not be r_null. What is new is that previously r_null was not a visitable r_sexp object and now it is.
Data frames
r_dfis now fully integrated into cppally.New variadic function
make_dfto create in-line data frames.Various
r_dfmembers have been added to allow easier data frame manipulation.
std::vector
- Partial support for
std::vectorcoercion. The followingstd::vectorcoercion directions are supported:-
std::vector->std::vector -
std::vector->cppally::r_vec -
cppally::r_vec->std::vector
-
Any coercion between std::vector and cppally::r_vec is possible so long as the element coercions are supported by cppally::as
Regular sequences
New function
seqwhich behaves similarly tobase::seq.New function
sequencewhich is similar tobase::sequencebut accepts only scalar inputs.
Named vector hash lookups
For named vectors, lookup by name has been dramatically improved in C++ by introducing a hashing approach. It works in the following way: the first time a lookup is requested, a linear scan is done to find the named value. The second time triggers the hash map of name-value pairs to be built and cached with the vector. That second lookup is completed using the cached hash map and all subsequent lookups also use the hash map. The rationale for hashing on second lookup is covered in the ‘Automatic Names Hashing’ vignette.
A similar hashing approach is also used for r_factors, making conversions of strings to and from factor codes fast and analytically viable.
Copy-on-modify
cppally now supports copy-on-modify as an opt-in feature. This feature prevents accidentally overwriting data between shared objects, just like R. To opt-in, run cppally::use_copy_on_modify or set the copy_on_modify to TRUE in cpp_source.
The major downside of this feature is significantly slower element setting as every set must verify the object is not referenced by another object. This check is single-threaded and thus nearly all parallel cppally code is disabled as a safety precaution. If using copy-on-modify, it is recommended to avoid writing cppally registered R functions that rely on in-place modification.
pmap
Inspired by purrr::pmap and base::mapply, cppally::pmap is a C++ variadic function that supports applying custom C++ lambda functions across corresponding elements of multiple vectors.
With pmap it is trivial to calculate parallel statistics like max, min, etc. Example of C++ version of base::pmax applied to two vectors.
reduce
A left-fold reduction functional that successively applies a binary function along the elements of the vector (from left-to-right).
Example: maximum value across vector of doubles
Other changes
New alias of
r_vec,r_vector.Named-vector subsetting is now supported.
New C++ functions
combineandflatten.combineis a variadic function that allows for combining multiple vectors into one, similar tobase::cbut always casts vectors to the common type among them.flattenallows one to flatten a list of vectors into one vector of a specified type, similar tounlist(recursive = FALSE).Many functions that were originally
r_vec-only members are now free functions that also work onr_sexpas well asRCompositetypes, allowing for easier manipulation of lists.All C++ reference qualifiers (T&, T&&, const T&) are now supported for registered functions, including templated ones.
New concept
RVectorisablewhich encompasses types that are OMP friendly.New infix operator
IS_IN, identical to R’s%in%.New C++ function
coalesce().r_psxct.datetime_str()always appends “UTC” at the end to avoid time-zone ambiguity.
Bug fixes
When registering C++ functions, cppally.hpp is now included in the generated C++ code. Not including it caused issues when trying to compile functions that constructed factors.
Zero-length
r_vecvectors can now be constructed unambiguously viar_vec<T>(0).Math operations involving mixed types that included
r_dblare now correct when involvingNAvalues.