I want to use an std::array
, but am operating on sufficiently large arrays that stack allocation is not reasonable (and fails using default stack size). I also needed a function that performed much like std::transform
, but always in-place. The issue is that many of these transformations change the "type". All of these types, in my usage, are simply the same templated type with different non-type template arguments associated with them. I don't want to have many arrays allocated to move data through the system because these type changes, only when a copy is strictly necessary.
To that end, I wrote a pretty crappy implementation of both by wrapping std::unique_ptr
and std::array
.
template <typename T, size_t SZ, typename Deleter = std::default_delete<T>>
class unique_heap_array {
private: // members
std::unique_ptr<std::array<T, SZ>> p {nullptr};
public: // pointer member types
using deleter_type = typename decltype(p)::deleter_type;
using element_type = typename decltype(p)::element_type;
using element_pointer_type = typename decltype(p)::pointer;
public: // array member types
using value_type = typename element_type::value_type;
using size_type = typename element_type::size_type;
using difference_type = typename element_type::difference_type;
using reference = typename element_type::reference;
using const_reference = typename element_type::const_reference;
using pointer = typename element_type::pointer;
using const_pointer = typename element_type::const_pointer;
using iterator = typename element_type::iterator;
using const_iterator = typename element_type::const_iterator;
using reverse_iterator = typename element_type::reverse_iterator;
using const_reverse_iterator = typename element_type::const_reverse_iterator;
public: // pointer member function
auto get_deleter() const noexcept { return p.get_deleter(); }
explicit operator bool() const noexcept { return p; }
auto borrow_ptr() const noexcept { return p.get(); }
auto release_ptr() noexcept { return p.release(); }
void reset_ptr(element_pointer_type ptr) noexcept { p.reset(ptr); }
void reset_ptr(std::nullptr_t ptr) noexcept { p.reset(ptr); }
void reset_ptr() noexcept { p.reset(new element_type); }
void swap(unique_heap_array& other) noexcept { p.swap(other.p); }
public: // array members functions
auto at(size_type i) { return p->at(i); }
auto at(size_type i) const { return p->at(i); }
auto operator[](size_type i) { return (*p)[i]; }
auto operator[](size_type i) const { return (*p)[i]; }
auto begin() { return p->begin(); }
auto begin() const { return p->begin(); }
auto cbegin() const { return p->cbegin(); }
auto end() { return p->end(); }
auto end() const { return p->end(); }
auto cend() const { return p->cend(); }
auto rbegin() { return p->rbegin(); }
auto rbegin() const { return p->rbegin(); }
auto crbegin() const { return p->crbegin(); }
auto rend() { return p->rend(); }
auto rend() const { return p->rend(); }
auto crend() const { return p->crend(); }
auto data() const { return p->data(); }
constexpr bool empty() const noexcept { return SZ == 0; }
constexpr auto size() const noexcept { return SZ; }
constexpr auto max_size() const noexcept { return SZ; }
void fill(const T& value) { p->fill(value); }
public: // constructors
unique_heap_array() { reset_ptr(); }
unique_heap_array(std::nullptr_t) { }
unique_heap_array(element_pointer_type ptr) { reset_ptr(ptr); }
public: // copy and move constructiona and assignment
unique_heap_array(const unique_heap_array<T, SZ, Deleter>&) = delete;
unique_heap_array(unique_heap_array<T, SZ, Deleter>&&) = default;
unique_heap_array<T, SZ, Deleter>& operator=(const unique_heap_array<T, SZ, Deleter>&) = delete;
unique_heap_array<T, SZ, Deleter>& operator=(unique_heap_array<T, SZ, Deleter>&&) = default;
public: // destructor
~unique_heap_array() = default;
public: // inplace data and type transformation
template <typename O, typename OutTypeDeleter = std::default_delete<O>, typename F>
auto transform(F&& func)
{
static_assert((sizeof(O) == sizeof(T)) &&
(alignof(O) <= alignof(T)),
"input and output types are not compatible");
auto res = unique_heap_array<O, SZ, OutTypeDeleter>(reinterpret_cast<std::array<O, SZ>*>(borrow_ptr()));
std::transform(begin(), end(), res.begin(), func);
release_ptr();
return res;
}
template <typename O, typename OutTypeDeleter = std::default_delete<O>, typename F, typename T2, typename Deleter2>
auto transform(F&& func, const unique_heap_array<T2, SZ, Deleter2>& other)
{
static_assert((sizeof(O) == sizeof(T)) &&
(alignof(O) <= alignof(T)),
"input and output types are not compatible");
auto res = unique_heap_array<O, SZ, OutTypeDeleter>(reinterpret_cast<std::array<O, SZ>*>(borrow_ptr()));
std::transform(begin(), end(), other.begin(), res.begin(), func);
release_ptr();
return res;
}
};
template <typename T, size_t SZ, typename Deleter>
bool operator==(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() == *b.borrow_ptr();
}
template <typename T, size_t SZ, typename Deleter>
bool operator!=(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() != *b.borrow_ptr();
}
template <typename T, size_t SZ, typename Deleter>
bool operator<(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() < *b.borrow_ptr();
}
template <typename T, size_t SZ, typename Deleter>
bool operator<=(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() <= *b.borrow_ptr();
}
template <typename T, size_t SZ, typename Deleter>
bool operator>(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() > *b.borrow_ptr();
}
template <typename T, size_t SZ, typename Deleter>
bool operator>=(const unique_heap_array<T, SZ, Deleter>& a, const unique_heap_array<T, SZ, Deleter>& b)
{
return *a.borrow_ptr() >= *b.borrow_ptr();
}
template <std::size_t i, typename T, size_t SZ, typename Deleter>
auto get(unique_heap_array<T, SZ, Deleter>& p) { return std::get<i>(*p.borrow_ptr()); }
template <std::size_t i, typename T, size_t SZ, typename Deleter>
auto get(const unique_heap_array<T, SZ, Deleter>& p) { return std::get<i>(*p.borrow_ptr()); }
The transform function works by reinterpreting the array as the output type, performing an std::transform
on the array in-place, then invalidating the input pointer afterward, returning the output type array created from the stolen pointer.
Example usage using similar types:
#include <cstdint>
template <int FracSize_>
struct FixedFractional {
static constexpr auto FracSize = FracSize_;
int64_t value;
};
template <int OutFracSize, int InFracSize>
FixedFractional<OutFracSize> resize(FixedFractional<InFracSize> a)
{
if (InFracSize < OutFracSize) {
return {a.value << (OutFracSize - InFracSize)};
} else {
return {a.value >> (InFracSize - OutFracSize)};
}
}
template <int AFracSize, int BFracSize>
auto operator+ (FixedFractional<AFracSize> a, FixedFractional<BFracSize> b)
{
constexpr auto ResFracSize = std::max(AFracSize, BFracSize);
auto resVal = resize<ResFracSize>(a).value + resize<ResFracSize>(b).value;
return FixedFractional<ResFracSize> {resVal};
}
int main()
{
auto a = unique_heap_array<FixedFractional<6>, 100'000>();
auto unaryXfer = [](decltype(a[0]) t) { return resize<4>(t); };
auto b = a.transform<decltype(unaryXfer(a[0]))>(unaryXfer);
// a is released, further usage is a seg fault
// b uses a's memory where it stores the resized integers from a
auto c = unique_heap_array<FixedFractional<12>, 100'000>();
auto binaryXfer = [](decltype(c[0]) t, decltype(b[0]) v) { return t + v; };
auto d = c.transform<FixedFractional<decltype(binaryXfer(c[0], b[0]))::FracSize>>(binaryXfer, b);
// c is now released
// d uses c's memory to store the piecewise addition of b and c
}
std::vector
code won't generate SIMD instructions due to the runtime size. Performance is a REQUIREMENT, hence the effort. \$\endgroup\$s
in the example is not defined within the scope of the example and possibly makes this question off-topic due toLack of Concrete Context
. Instead of the 2 line example could you provide a test case that actually runs? \$\endgroup\$