cpp11 icon indicating copy to clipboard operation
cpp11 copied to clipboard

Dimension error with `r_vector`

Open fangzhou-xie opened this issue 5 months ago • 1 comments

Hi, thanks for this wonderful package. As I was playing around with the r_vector class, I found the following peculiar behavior and I think this might be a bug. Specifically, there should only be 6 elements, but when writing the dimensions to the vector, it returns an error saying there's 8 elements.

cpp11::cpp_function(
"
SEXP test() {
  using namespace cpp11;
  writable::r_vector<double> vec;  // Start empty

  // Dynamically add data
  vec.push_back(1.0);
  vec.push_back(2.0);
  vec.push_back(3.0);
  vec.push_back(4.0);
  vec.push_back(5.0);
  vec.push_back(6.0);

  message(\"size: %i\", vec.size());

  // Now decide dimensions (2 rows by 3 columns)
  vec.attr(R_DimSymbol) = writable::r_vector<int>({2, 3});

  // Create matrix from the vector
  // writable::doubles_matrix<> mat(vec.data());

  return(vec.attr(R_DimSymbol));
}
"
)
test()
#> size: 6
#> Error in test(): dims [product 6] do not match the length of object [8]

Created on 2025-07-21 with reprex v2.1.1

Disclaimer: This example was taken from some code snippets from Claude while I was trying to understand the internals of this package.

fangzhou-xie avatar Jul 22 '25 00:07 fangzhou-xie

hi @fangzhou-xie

When you set attributes on an R vector, R "sees" the underlying SEXP which has full capacity (8 elements), not just the length (6 elements) that you've actually used. This explains the behaviour: https://github.com/r-lib/cpp11/blob/main/inst/include/cpp11/r_vector.hpp#L1058

The "correct" way would be copy the object as a SEXP:

SEXP test() {
  using namespace cpp11;
  writable::r_vector<double> vec;
  
  vec.push_back(1.0);
  vec.push_back(2.0);
  vec.push_back(3.0);
  vec.push_back(4.0);
  vec.push_back(5.0);
  vec.push_back(6.0);
  
  SEXP vec_sexp = vec;
  Rf_setAttrib(vec_sexp, R_DimSymbol, writable::r_vector<int>({2, 3}));
  
  return vec_sexp;
}

or to avoid copies to be explicit with the resize:

SEXP test() {
  using namespace cpp11;
  writable::r_vector<double> vec;
  
  vec.push_back(1.0);
  vec.push_back(2.0);
  vec.push_back(3.0);
  vec.push_back(4.0);
  vec.push_back(5.0);
  vec.push_back(6.0);
  
  // Explicitly resize to current length (truncates capacity)
  vec.resize(vec.size());
  
  vec.attr(R_DimSymbol) = writable::r_vector<int>({2, 3});
  
  return vec;
}

pachadotdev avatar Aug 26 '25 19:08 pachadotdev