0

In my C++20 project, I have a data container class Tensor which handles multi-dimensional arrays. It is implemented by having a raw pointer to some slab of memory, and then it provides various form of access to it, and also methods for printing stuff, resetting it to zero, and so on.

The class doubles as its own view. This is done with the boolean field owns_allocation_ which tracks which Tensor instance is responsible for deallocating the memory. This works fine, except for one detail: I cannot make a proper read-only view of a slice of a Tensor object. The closest I get is the following:

#include <vector>

class Tensor {
public:
  Tensor(std::vector<int> const &shape)
      : shape_(shape), owns_allocation_(true) {
    int size = 1;
    for (int const dim : shape) {
      size *= dim;
    }
    data_ = new double[size];
  }

  Tensor(double *data, std::vector<int> const &shape, bool owns_allocation) {
    data_ = data;
    shape_ = shape;
    owns_allocation_ = owns_allocation;
  }

  Tensor(Tensor const &other) = delete;

  // Move constructor and other methods …

  ~Tensor() {
    if (owns_allocation_) {
      delete[] data_;
    }
  }

  double &at_flat(std::size_t const index) { return data_[index]; }

  // Here is the problem, the return type of `Tensor const` is just `Tensor`.
  Tensor const slice(std::vector<int> at, std::size_t const size) const {
    auto const offset = compute_offset(shape_, at);
    auto const new_shape = compute_slice_shape(shape_, at, size);
    return Tensor(data_ + offset, new_shape, false);
  }

  // More handy methods …

private:
  double *data_ = nullptr;
  std::vector<int> shape_;
  bool owns_allocation_;
};

void break_const(Tensor const &tensor) {
  auto view = tensor.get_view();
  view.at_flat(0) = 1.0;
}

int main() {
  Tensor tensor({2, 2});
  break_const(tensor);
}

But because a return type of Tensor const doesn't have any influence on the caller, it will end up as a modifiable Tensor instance and I get change the state of the object.

I could write a separate TensorView class, which would re-implement all the methods which are already marked const on Tensor. But that seems like a violation of the “don't repeat yourself” principle.

The best idea I have so far is making a TensorView class and derive a more general (mutable) Tensor from that. Then all the const methods would go into TensorView and be automatically inherited. The data fields would be protected. Would that make sense? Or is there something better?

Martin Ueding
  • 8,245
  • 6
  • 46
  • 92
  • Why are returning a copy of `Tensor` in the first place? You could as well just return a reference to `*this`, I don't see the point of your `get_view()` implementation... in my view the name is a misnomer, because what you're doing is simply returning a (flat) copy. Btw. [do not return `const` _values_](https://stackoverflow.com/questions/8716330/purpose-of-returning-by-const-value). – andreee Oct 14 '22 at 15:48
  • Also, it looks like you are trying to implement some sort of ownership, managed by your `Tensor` class. That is not an ideal solution. If you want your class to have a unique owner, instantiate it using a `unique_ptr`. "Viewers" can then simply use `myTensorUniquePtr.get()` to access the raw pointer. That way, you leave ownership to the standard library (separation of concerns!) and focus on your `Tensor` class, which should not care about any ownership. I hope this helps. – andreee Oct 14 '22 at 15:56
  • Last but not least, I'd recommend [this blog entry](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/) by Herb Sutter. In particular, the abswer to "Guru question 3" should be useful. – andreee Oct 14 '22 at 16:00
  • @andreee: The `get_view()` was indeed an insufficient example. I have converted that to the `slice()` method which should make a read-only view onto part of the tensor. This isn't possible by `return *this` alone. — This also wouldn't work with a `unique_ptr` because some `Tensor` instances need to be non-owning in the current setup. – Martin Ueding Oct 14 '22 at 16:04

0 Answers0