C++ construct vector from raw array without copy

Viewed 786

Is it possible to construct a vector from a raw pointer without copy?

I know the vector itself has ownership semantics, it allocate space when constructed, and responsible for releasing that memory when destroyed. But that's not what I want, what I want to do is more like an adapter, it has no ownership regarding the underlying data, just make some wrap around that data, so that : 1. easier to use, 2. do some boundry check.

The reason why I want to do such thing is that potential users of my library might have eigen, numpy, opencv, or something else. I want to make a more flexible way to pass data without copy. Raw pointer can do it, but I want to do some boundry check, otherwise out-of-bound error in someone else's code will finally lead to a crash inside my library. If I can create such no-ownership container then everything would be perfect.

1 Answers

This is quite easy as long as you don't require ownership, as you mention. The solution is to use a type-erased contiguous sequence. If at some point you do desire ownership, it gets a bit more complicated. I've added information on this to the bottom of this answer.

Non-owning

If you are using , you can make use of std::span -- which is a non-owning wrapper around contiguous data such as std::vector, std::array, etc.

If you are using anything prior to c++20, you can always use or base an implementation off of a pre-canned span implementation, such as gsl::span from the Guideline Support Library, or bpstd::span from BackportC++ which is compatible.

Or alternatively find any array_view-like type which accomplishes the same thing. There are many existing open-source solutions that can be leveraged for this purpose.

Owning

If you do require ownership, then more work will be involved -- especially if you want the data to come from different contiguous sequence types.

You will require owning type-erasure for such a purpose, and at some point a vector will either need to be moved or copied -- one of the two. If you can allow the clients the option to move their vectors, this would be much cheaper than performing a deep copy of the data.

To do this, you will need to have an interface, and a class-template implementation type, all wrapped into a nice "vector"-like API. The captured data can then be held in the class-template implementation indirectly, behind a unique_ptr:

template <typename T>
class AnyVector
{
public:
   template <typename Container>
   explicit AnyVector(Container&& container)
       : m_container{std::make_unique<Concrete<std::decay_t<Container>>(std::forward<Container>(container))}
   {
   }

   const T& operator[](std::size_t index) const
   {
       return m_container->get(index);
   }
   const T& at(std::size_t index) const
   {
       if (index >= m_container->size()) {
           throw std::out_of_range{"AnyVector<T>::at"};
       }
       return m_container->get(index);
   }
   std::size_t size() const
   {
        return m_container->size();
   }

private:

   // The interface we want all types to follow
   class Interface
   {
   public:
       // Make this API as deep as you need it to be
       virtual ~Interface() = default;
       virtual const T& get(std::size_t index) const = 0;
       virtual std::size_t size() const = 0;
   };

   // The concrete version of the interface, in terms of the underlying container
   template <typename Underlying>
   class Concrete : public Interface
   {
   public:
      template <typename TUnderlying>
      Concrete(TUnderlying&& underlying) 
        : m_underlying{std::forward<TUnderlying>(underlying)}
      {
      }
      
       virtual const T& get(std::size_t index) const override { return m_underlying[index]; }
       virtual std::size_t size() const override { return m_underlying.size(); }
   private:
       Underlying m_underlying;
   }

   std::unique_ptr<Interface> m_container;
};

The above code only works if the input container defines a T::size() and T::operator[] function. If the underlying vectors you want to own have functions that are differently named, you will need to be more creative in solving this -- either through having the user explicitly specify with a traits-like type, or alternatively using non-member functions with ADL.

In the latter case, this could be achieved by doing something like:

    std::size_t size() const override
    {
        // ADL-find 'size'
        return size(m_container);
    }
Related