Skip to content

Commit 878bea9

Browse files
committed
document sugar operand lengths, lifetimes, and assignment
1 parent 60f24be commit 878bea9

2 files changed

Lines changed: 109 additions & 7 deletions

File tree

‎ChangeLog‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
2026-09-30 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/traits/sugar_operand.h: New trait for how sugar
4+
expressions hold their operands: vectors by reference, and nested
5+
expressions and views by value, so stored expressions don't dangle
6+
* inst/include/Rcpp/traits/is_elementwise.h: New trait for whether a
7+
sugar expression can be written in place into storage it may read from
8+
* inst/include/Rcpp/sugar/tools/check_sizes.h: New helpers checking that
9+
sugar operands, and vectors assigned into views, have matching lengths
10+
* inst/include/RcppCommon.h: Include the above
11+
* inst/include/Rcpp/sugar/sugar_forward.h: Idem
12+
* inst/include/Rcpp/sugar/: Hold operands via sugar_operand, declare
13+
elementwise operands, and check operand lengths
14+
* inst/include/Rcpp/stats/dpq/dpq.h: Idem
15+
* inst/include/Rcpp/sugar/block/SugarBlock_3.h: Also fix the type of the
16+
third operand
17+
* inst/include/Rcpp/vector/Vector.h: Evaluate sugar expressions that
18+
aren't elementwise before assigning them in place
19+
* inst/include/Rcpp/vector/RangeIndexer.h: Idem, and check lengths
20+
* inst/include/Rcpp/vector/MatrixColumn.h: Idem
21+
* inst/include/Rcpp/vector/MatrixRow.h: Idem
22+
* inst/include/Rcpp/vector/MatrixBase.h: Add a const get_ref()
23+
* inst/tinytest/cpp/sugar_expressions.cpp: New tests
24+
* inst/tinytest/test_sugar.R: Idem
25+
* vignettes/rmd/Rcpp-sugar.Rmd: Document operand lengths, storing
26+
expressions, and assignment
27+
128
2026-09-22 Iñaki Ucar <iucar@fedoraproject.org>
229

330
* inst/include/Rcpp/sugar/matrix/col.h: Fix Col constructor using ncol()

‎vignettes/rmd/Rcpp-sugar.Rmd‎

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ arithmetic expression must be of the same type (for example they should be both
191191

192192
The lhs and the rhs can either have the same size or one of them could
193193
be a primitive value of the appropriate type, for example adding a
194-
`NumericVector` and a `double`.
194+
`NumericVector` and a `double`. Unlike \proglang{R}, \sugar does not recycle
195+
vectors of different sizes; see the section on expressions and assignment.
195196

196197
## Binary logical operators
197198

@@ -518,6 +519,69 @@ functions (\textsl{i.e.} you) should place an \code{RNGScope} at the
518519
appropriate level of your code.
519520

520521

522+
# Expressions and assignment
523+
524+
\sugar expressions are evaluated lazily: `x + y` builds an object describing
525+
the computation, and its elements are only computed when the expression is
526+
assigned to a vector or otherwise consumed, for example by `sum()`.
527+
528+
## Operand lengths
529+
530+
Unlike \proglang{R}, \sugar does not recycle: the vectors combined by an
531+
expression must all have the same length, and an error is signalled
532+
otherwise. Likewise, a vector assigned into a range, row or column (as in
533+
`m(_, 0) = x`) must have the length of that target. Single values, like the
534+
`2.0` in `x + 2.0`, can be combined with vectors of any length.
535+
536+
## Storing expressions
537+
538+
An expression holds the vectors it reads by reference, and any nested
539+
expressions by value. It can be stored, for example in an `auto` variable,
540+
and evaluated later, as long as the vectors it reads are still alive:
541+
542+
```cpp
543+
NumericVector x, y;
544+
545+
// fine: x and y outlive e
546+
auto e = x * 2.0 + y;
547+
NumericVector z = e;
548+
```
549+
550+
An expression reading from a temporary vector, however, has to be
551+
evaluated in the statement that creates it:
552+
553+
```cpp
554+
// wrong: the clone is destroyed at the end of
555+
// the statement, while e still refers to it
556+
auto e = clone(x) + 1.0;
557+
558+
// fine
559+
NumericVector z = clone(x) + 1.0;
560+
```
561+
562+
## Assignment
563+
564+
Assigning an expression to an existing vector of the same length writes the
565+
result into that vector's storage. As with any modification made through an
566+
\pkg{Rcpp} vector, this is visible through every other reference to the same
567+
\proglang{R} object, including the argument passed in from \proglang{R}:
568+
569+
```cpp
570+
// [[Rcpp::export]]
571+
void twice(NumericVector x) {
572+
// modifies the caller's vector
573+
x = x * 2.0;
574+
}
575+
```
576+
577+
When the lengths differ, a new vector is allocated instead.
578+
579+
An expression may read from the vector it is assigned to, as in
580+
`x = rev(x)`. Expressions whose elements only depend on the corresponding
581+
elements of their operands (arithmetic, comparisons, mathematical functions,
582+
`ifelse()`, and so on) are written directly; others are first evaluated into
583+
a temporary vector.
584+
521585
# Performance
522586
\label{sec:performance}
523587
@@ -658,7 +722,7 @@ public:
658722
RESULT_R_TYPE>::type STORAGE;
659723

660724
Sapply(const VEC& vec_, Function fun_) :
661-
vec(vec_), fun(fun_){}
725+
vec(vec_.get_ref()), fun(fun_){}
662726

663727
inline STORAGE operator[]( int i ) const {
664728
return converter_type::get(fun(vec[i]));
@@ -669,7 +733,7 @@ public:
669733
}
670734

671735
private:
672-
const VEC& vec;
736+
typename Rcpp::traits::sugar_operand<VEC>::type vec;
673737
Function fun;
674738
};
675739

@@ -801,18 +865,22 @@ is the manifestation of the _CRTP_.
801865
802866
803867
The constructor of the `Sapply` class template is straightforward, it
804-
simply consists of holding the reference to the input expression and the
805-
function.
868+
simply consists of holding the input expression and the function.
806869
807870
```cpp
808871
Sapply(const VEC& vec_, Function fun_):
809-
vec(vec_), fun(fun_){}
872+
vec(vec_.get_ref()), fun(fun_){}
810873
811874
private:
812-
const VEC& vec;
875+
typename Rcpp::traits::sugar_operand<VEC>::type vec;
813876
Function fun;
814877
```
815878

879+
The input is held through `sugar_operand`: by reference when it is a vector,
880+
and by value when it is itself a \sugar expression. Those are typically
881+
temporaries, so holding them by reference would leave `Sapply` referring to
882+
destroyed objects once the statement that created it ends.
883+
816884
### Implementation
817885

818886
The indexing operator and the `size` member function is what
@@ -830,6 +898,13 @@ inline int size() const {
830898
}
831899
```
832900

901+
Expressions whose $i^{\text{th}}$ element only depends on the
902+
$i^{\text{th}}$ elements of their operands can say so by declaring
903+
`typedef Rcpp::traits::elementwise_operands<VEC> rcpp_elementwise`, which
904+
lets them be written in place when assigned to one of those operands.
905+
`Sapply` does not, since the function it applies may read any element of
906+
the vector being assigned to.
907+
833908
# Summary
834909

835910
TBD

0 commit comments

Comments
 (0)