Using std::vector as view on to raw memory
C++20's std::span
If you are able to use C++20, you could use std::span
which is a pointer - length pair that gives the user a view into a contiguous sequence of elements. It is some sort of a std::string_view
, and while both std::span
and std::string_view
are non-owning views, std::string_view
is a read-only view.
From the docs:
The class template span describes an object that can refer to a contiguous sequence of objects with the first element of the sequence at position zero. A span can either have a static extent, in which case the number of elements in the sequence is known and encoded in the type, or a dynamic extent.
So the following would work:
#include <span>
#include <iostream>
#include <algorithm>
int main() {
int data[] = { 5, 3, 2, 1, 4 };
std::span<int> s{data, 5};
std::sort(s.begin(), s.end());
for (auto const i : s) {
std::cout << i << "\n";
}
return 0;
}
Check it out live
Since std::span
is basically pointer - length pair, you can use in a following manner too:
size_t size = 0;
int *data = get_data_from_library(size);
std::span<int> s{data, size};
Note: Not all compilers support std::span
. Check compiler support here.
UPDATE
If you are not able to use C++20, you could use gsl::span
which is basically the base version of the C++ standard's std::span
.
C++11 solution
If you are limited to C++11 standard, you can try implementing your own simple span
class:
template<typename T>
class span {
T* ptr_;
std::size_t len_;
public:
span(T* ptr, std::size_t len) noexcept
: ptr_{ptr}, len_{len}
{}
T& operator[](int i) noexcept {
return *ptr_[i];
}
T const& operator[](int i) const noexcept {
return *ptr_[i];
}
std::size_t size() const noexcept {
return len_;
}
T* begin() noexcept {
return ptr_;
}
T* end() noexcept {
return ptr_ + len_;
}
};
Check out C++11 version live
The problem is that std::vector
has to make a copy of the elements from the array you initialize it with as it has the ownership of the objects it contains.
To avoid this, you can use a slice object for an array (i.e., similar to what std::string_view
is to std::string
). You could write your own array_view
class template implementation whose instances are constructed by taking a raw pointer to an array's first element and the array length:
#include <cstdint>
template<typename T>
class array_view {
T* ptr_;
std::size_t len_;
public:
array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}
T& operator[](int i) noexcept { return ptr_[i]; }
T const& operator[](int i) const noexcept { return ptr_[i]; }
auto size() const noexcept { return len_; }
auto begin() noexcept { return ptr_; }
auto end() noexcept { return ptr_ + len_; }
};
array_view
doesn't store an array; it just holds a pointer to the beginning of the array and the length of that array. Therefore, array_view
objects are cheap to construct and to copy.
Since array_view
provides the begin()
and end()
member functions, you can use the standard library algorithms (e.g., std::sort
, std::find
, std::lower_bound
, etc.) on it:
#define LEN 5
auto main() -> int {
int arr[LEN] = {4, 5, 1, 2, 3};
array_view<int> av(arr, LEN);
std::sort(av.begin(), av.end());
for (auto const& val: av)
std::cout << val << ' ';
std::cout << '\n';
}
Output:
1 2 3 4 5
Use std::span
(or gsl::span
) instead
The implementation above exposes the concept behind slice objects. However, since C++20 you can directly use std::span
instead. In any case, you can use gsl::span
since C++14.
Since the algorithm-library works with iterators you can keep the array.
For pointers and known array length
Here you can use raw pointers as iterators. They support all the opertations an iterator supports (increment, comparison for equality, value of, etc...):
#include <iostream>
#include <algorithm>
int *get_data_from_library(int &size) {
static int data[] = {5,3,2,1,4};
size = 5;
return data;
}
int main()
{
int size;
int *data = get_data_from_library(size);
std::sort(data, data + size);
for (int i = 0; i < size; i++)
{
std::cout << data[i] << "\n";
}
}
data
points to the dirst array member like an iterator returned by begin()
and data + size
points to the element after the last element of the array like an iterator returned by end()
.
For arrays
Here you can use std::begin()
and std::end()
#include <iostream>
#include <algorithm>
int main()
{
int data[] = {5,3,2,1,4}; // raw data from library
std::sort(std::begin(data), std::end(data)); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
}
But keep in mind that this only works, if data
does not decay to a pointer, because then length information goes missing.