5

I want to write a function which:

  1. Takes a pointer as a parameter
  2. Takes a length as a parameter
  3. Owns the memory pointed to by the pointer (e.g. maybe it releases it, or constructs a unique_ptr to it in some data structure etc.)

Now, if I wanted 1+2 I would just use gsl::span. And if wanted 1+3 I would use owner<T*>. But what should I do when I want all three? Should I pass an owner<gsl::span<T>>? Something else?

Notes:

  • You may not assume the pointer is into the heap.
  • std::vector is requiring too much. The function should not require the caller to construct an std::vector.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 6
    gsl::span means "do not take ownership". You want to take ownership; Therefore, do not use gsl::span; If you want to take ownership of a [pointer + length] set of data, store them into a tuple of [std::unique_ptr, length] (and receive that tuple into your function (though probably tuple is a bad idea - define a proper type instead). – utnapistim Oct 03 '16 at 09:26
  • 1
    owning pointer + length is pretty much `std::vector` or `std::experimental::dynarray`... – Ap31 Oct 03 '16 at 09:54
  • @Ap31: 1. Nobody said the pointer is into the heap. 2. Vector is too semantically rich for my taste here. 3. Dynarray sounds interesting, consider elaborating please. – einpoklum Oct 03 '16 at 10:37
  • @utnapistim: I was under the impression that gsl::span just means non-owning pointer + length. – einpoklum Oct 03 '16 at 10:37
  • `gsl::span` is a way of hiding the implementation of a container at zero cost. It is not a choice of container itself. Do you want the caller of the function to be able to change the implementation of the container? Or is it known to your function at compile time what the choice of container actually is. – Chris Drew Oct 03 '16 at 10:47
  • @ChrisDrew: I was just thinking of gsl::span as a way to avoid passing a raw pointer and a number. – einpoklum Oct 03 '16 at 10:51
  • 2
    Well, regardless of taste, to own a contiguous memory block of dynamic size in a modern C++ one uses `std::vector` by default. Any other solutions are valid (we're still in C++ :)), but should be justified. GSL instruments are not a replacement for C++ standard library, but rather an addition that allows to cope with legacy APIs and legacy implementations without risky refactorings. With that in mind, I'd like to clarify: do you have any specific requirement for your API (other than the taste) that would deny usage of `std::vector`? – Vasiliy Galkin Jul 19 '17 at 20:15
  • @VasiliyGalkin: I beg to differ. `std::vector` is a dynamically-sized container, typically on the heap (although you could have the uncommon use of allocators). That has nothing to do with what my use case. – einpoklum Jul 19 '17 at 20:59
  • Hm, okay, let me try to reiterate then. The requirement in your question states: "Takes a pointer as a parameter. Takes a length as a parameter". This normally makes me think of a dynamically allocated array (or, the need to provide C-compatible interface). You state that you need a memory on stack. If you don't need dynamic allocation as well, then I would guess why would `std::array` **not** fit your needs? If you **do need** dynamic allocation **and** memory on stack, well...then it's by itself a complicated story. – Vasiliy Galkin Jul 20 '17 at 13:12

1 Answers1

1

One option would be to define your own abstract base class to encapsulate the data. Something like:

template<typename T>
class DataHolder {
public:
  virtual ~DataHolder() = default;
  virtual gsl::span<T> get() const = 0;
};

Then your function could look something like:

void foo(std::unique_ptr<DataHolder<int>> data) {
  if (!data)
    return;
  for (auto v : data->get())
    std::cout << v << " ";
}

The caller can then implement the base class with any container they want to. There will be a small cost of polymophism but not on a per-element basis.

If you don't want to pay for polymorphism, perhaps you could make your function accept a template parameter.

template<typename DataHolder>
void foo(DataHolder data) {
  for (auto v : data())
    std::cout << v << " ";
}

where the implicit interface for DataHolder could be satisfied by something like:

struct VectorHolder {
    std::vector<int> data;
    gsl::span<const int> operator()() const { return data; }
};

or if you really don't want to use vector. You could use something like this (as suggested by @utnapistim):

struct ArrayHolder {
    std::unique_ptr<int[]> data;
    ptrdiff_t              length;
    gsl::span<const int> operator()() const { return {data.get(), length}; }
};
Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • I'm not going to define per-function abstract base classes that are specific to my own code. Also, I don't see why I would want the double-indirection of a unique_ptr to a span. – einpoklum Oct 04 '16 at 19:49
  • The `unique_ptr` is to manage the memory of the data which the span can't manage itself. – Chris Drew Oct 04 '16 at 20:49
  • You still have a pointer-to-a-pointer, which is semantically gratuituous. With due respect - I odn't like this solution. – einpoklum Oct 04 '16 at 21:12