1

I am trying to implement the following function:

template<typename T, typename... ARGUMENTS>
std::unique_ptr<T[], HeapDeleter<T[]>> allocate_array( ARGUMENTS&&... arguments ) {
HeapAllocator<T> allocator;

/* Allocate array which size is equal to number of arguments given */
T* ptr = allocator.template allocate<sizeof...(ARGUMENTS)>();
if( ptr != nullptr ) {
/* TODO: There comes the magic... */
}

First step is to allocate memory for the array using allocate<>() call. This should already be clear but what I am about to add is to call the constructor for every array element within the allocated memory using the placement new. There exactly I would like to kindly ask for help...

Basically for a single element I can write:

::new( static_cast<void*>( ptr ) T( /* One argument from arguments given */ );

The idea is to initialize n-th element with n-th argument within the arguments list on ptr+n address, ideally using move constructor to avoid unnecessary copies.

I hope you got the idea... Many thanks in advance for help!

Martin

2 Answers 2

2

A chance to utilise fold expressions. Yay :)

You can use comma operator to repeat operation and a counter for increasing index:

std::ptrdiff_t i = 0;
(::new(ptr + i++) T(arguments), ...);
Sign up to request clarification or add additional context in comments.

4 Comments

@uneven_mark I've been wondering, in what case does qualification of the placement new make a difference? As for arguments, I didn't notice that the code had a scroll bar, so it was hidden from me. Should be trivial to map my example to their use case however.
Although the new-expression used here maps to a non-replaceable global operator new, I didn't find anything prohibiting class-specific overloads matching it. Therefore new will use class-specific operator new overloads of T, while ::new wont. (From what I can tell.)
Now that I think about it, that may also be the reason for static_cast<void*> in OP's code, as it prevents other global user-defined operator new to be a better match than the built-in one.
@uneven_mark feels like similar level of carefulness as using std::addressof instead of operator&. Probably good idea for a library though.
2

With Fold expression:

T* ptr = allocator.template allocate<sizeof...(ARGUMENTS)>();
T* p = ptr;

((::new (static_cast<void*>(p++) T(arguments)), ...);

4 Comments

How is this gonna expand? Won't it end up as, for example, ((::new (static_cast<void*>(p++) T(arg1), (::new (static_cast<void*>(p++) T(arg2))) and so on, which would introduce multiple p++ per sequence point?
@JHBonarius: I suppose OP needs original ptr for return.
@JHBonarius That’s because the original ptr is returned from the allocation, the fold expression is used just to call the constructors. One the ptr is modified by the pointer arithmetics, there is nothing to return later... refer to function signature.
Then I would const qualify it ;)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.