1

Is there some way to create an array in C++ where we don't know the type, but we do know it's size and alignmnent requirements?

Let's say we have a template:

template<typename T>
T* create_array(size_t numElements) { return new T[numElements]; }

This works because each element T has known size and alignment, which is known at compile-time. But I'm looking for something where we can delegate the creation for later by simply extracting size and align and passing them on. This is the interface that I seek:

// my_header.hpp

// "internal" helper function, implementation in source file!
void* _create_array(size_t s, size_t a, size_t n);

template<typename T>
T* create_array(size_t numElements) {
    return (T*)_create_array(sizeof(T), alignof(T), numElements);
}

Can we implement this in a source file?:

#include "my_header.hpp"

void* _create_array(size_t s, size_t a, size_t n) {
    // ... ?
}
Requirements:
  • Each array element must have the correct alignment.
  • The total array size must be equal to s*n, and be aligned to a.
  • Type safety is assumed to be managed by the templated interface.
  • Indexing into the array should use correct size and align offsets.

I'm using C++20, so newer features may also be considered. In advance, thank you!

8
  • 3
    You could create an array using std::aligned_storage::type as the element type, and then placement-new your actual objects onto those array elements afterwards as needed. Except that the size and alignment values for aligned_storage need to be template parameters, not runtime function parameter. Hmm... Commented Jul 29, 2022 at 21:36
  • 2
    Looks uncannily like std::vector ... Commented Jul 29, 2022 at 21:41
  • Honestly, the more times I read this, the more I'm certain that (1) your actual design problem could benefit from some application of type erasure (2) you are fixated on a solution to the actual problem, so you ask about that instead. Commented Jul 29, 2022 at 21:44
  • 2
    @RemyLebeau you could do exactly what you described with aligned_alloc, no compile-time type required. Commented Jul 29, 2022 at 21:56
  • 2
    As far the second point you think about, have you seen <memory_resource>? It's the library spec and support for polymorphic allocators. You might be able to draw up some inspiration there. Commented Jul 29, 2022 at 23:22

1 Answer 1

1

While you can also implement this yourself, you can simply use std::allocator:

template<typename T>
constexpr T* create_array(size_t numElements) {
    std::allocator<T> a;
    return std::allocator_traits<decltype(a)>::allocate(a, numElements);
}

and then

template<typename T>
constexpr void destroy_array(T* ptr) noexcept {
    std::allocator<T> a;
    std::allocator_traits<decltype(a)>::deallocate(a, ptr);
}

The benefit over doing it yourself via a call to operator new is that this will also be usable in constant expression evaluation.

You then need to create objects in the returned storage via placement-new, std::allocator_traits<std::allocator<T>>::construct or std::construct_at.

Anyway, first make sure that you really need to do all of this memory management manually. Standard library containers already offer similar functionality, e.g. std::vector has a .reserve member function to reserve memory in which objects can be placed later via push_back, emplace_back, resize, etc.


If you want to implement the above yourself, you basically need

#include<new>

//...

void* create_array(size_t s, size_t a, size_t n) {
    // CAREFUL: check here that `s*n` does not overflow! Potential for vulnerabilities!
    return ::operator new(s*n, std::align_val_t{a});
}

void destroy_array(void* ptr, size_t a) noexcept {
    ::operator delete(ptr, std::align_val_t{a});
}

(Note that identifiers starting with an underscore are reserved in the global namespace scope and may not be used there as function names, so I changed the name.)

Sign up to request clarification or add additional context in comments.

5 Comments

I thank you for the suggestion, though I was looking for a non-generic solution which strips away any kind of template. It looks like decltype(a) is still dependent upon a template. And yes, I'm building custom allocators, and I'm simply researching and trying to educate myself in the possibilites. Being able to implement everything in a source file (not a header) would be a huge bonus for me. If that's not entirely possible that's okay.
@alexpanter See my edit, but I don't really see how this helps you in any way. There is basically no cost to having the allocation templated on the type and you need to cast to the target type somewhere anyway.
Yeah the cast away from void* is my main headache. This is a difficult problem, and in time I will figure out a good solution. But there are costs involved with allocation on the templated type, including code size and compilation times, both of which are a concern to me. If I'm not wrong that is. Part of the learning process is figuring out the correct questions to ask. You were very helpful!
@alexpanter It is very likely that the allocate calls from std::allocator will be inlined everywhere anyway, so that in the end there will only be calls to ::operator new one way or another and no additional code. These calls can't be inlined because they are globally replaceable by the user. If you put create_array in a .cpp instead of a header it can't be inlined without LTO, so you will very likely have another function call indirection that isn't really needed. (The exception to this is probably the overflow check in std::allocator which might increase code at the call site.)
Good point! I will need to rely on LTO anyway, since I need the possibility of delegating memory blocks to sub-allocators. So I can't guarantee that all allocations will be using new. And great that you pointed out the overflow risk - I will definitely have both s and a be U32, and the product of them U64.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.