Background: this is a followup to @pari's answer to Constant-sized vector.
My type, metadata_t
does not have a default constructor.
I use std::make_unique
for fixed-size arrays that the size is not available in compile-time. (ie const size).
typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;
fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
// note: this is run-time:
auto n = array.size();
return fixedsize_side_metadata_t(array.begin(), array.end()); // error
return fixedsize_side_metadata_t(array); // error
return std::unique_ptr<metadata_t[]>(0); // no error, but not useful
return std::unique_ptr<metadata_t[]>(n); // error
}
However, the constructor of unique_ptr<...[]>
only accepts a size (integer). How do I initialise and copy my vector into my unique_ptr<[]>
?
I tried std::unique_ptr<metadata_t[]>(array.size());
to prepare and then copy/populate the contents in the next step, but it shows a compile error.
Note: I use C 20 (or higher if it was available). Could make_unique_for_overwrite
be useful? ( C 23 ).
Note: At first I thought generate
(as in this answer) can do it, but it does not solve my problem because n
is a run-time information.
The size of my vector is determined at run-time.
The whole point of this function is to convert my std::vector
into a fixed-size data structure (with run-time size).
The data structure does not have to be unique_ptr<T[]>
.
The old title referred to unique_ptr
but I am really looking for a solution for a fixed-size data structure. So far, it is the only data structure I found as a "constant-size indexed container with size defined at runtime".
CodePudding user response:
You can't initialize the elements of a unique_ptr<T[]>
array with the elements of a vector<T>
array when constructing the new array (UPDATE: apparently you can, but it is still not going to be a solution solved with a single statement, as you are trying to do).
You will have to allocate the T[]
array first, and then copy the vector
's elements into that array one at a time, eg:
typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;
fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
fixedsize_metadata_t result = std::make_unique<metadata_t[]>(array.size());
std::copy(array.begin(), array.end(), result.get());
return result;
}
UPDATE: you updated your question to say that metadata_t
does not have a default constructor. Well, that greatly complicates your situation.
The only way to create an array of objects that don't support default construction is to allocate an array of raw bytes of sufficient size and then use placement-new
to construct the individual objects within those bytes. But now, you are having to manage not only the objects in the array, but also the byte array itself. By itself, unique_ptr<T[]>
won't be able to free that byte array, so you would have to provide it with a custom deleter
that frees the objects and the byte array. Which also means, you will have to keep track of how many objects are in the array (something new[]
does for you so delete[]
works, but you can't access that counter, so you will need your own), eg:
struct metadata_arr_deleter
{
void operator()(metadata_t *arr){
size_t count = *(reinterpret_cast<size_t*>(arr)-1);
for (size_t i = 0; i < count; i) {
arr[i]->~metadata_t();
}
delete[] reinterpret_cast<char*>(arr);
}
};
typedef std::unique_ptr<metadata_t[], metadata_arr_deleter> fixedsize_metadata_t;
fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
const auto n = array.size();
const size_t element_size = sizeof(std::aligned_storage_t<sizeof(metadata_t), alignof(metadata_t)>);
auto raw_bytes = std::make_unique<char[]>(sizeof(size_t) (n * element_size));
size_t *ptr = reinterpret_cast<size_t*>(raw_bytes.get());
*ptr = n;
auto *uarr = reinterpret_cast<metadata_t*>(ptr);
size_t i = 0;
try {
for (i = 0; i < n; i) {
new (&uarr[i]) metadata_t(array[i]);
}
}
catch (...) {
for (size_t j = 0; j < i; j) {
uarr[j]->~metadata_t();
}
throw;
}
raw_bytes.release();
return fixedsize_metadata_t(uarr);
}
Needless to say, this puts much more responsibility on you to allocate and free memory correctly, and it is really just not worth the effort at this point. std::vector
already supports everything you need. It can create an object array using a size known at runtime, and it can create non-default-constructable objects in that array, eg.
std::vector<metadata_t> consolidate(const std::vector<metadata_t> &array) {
auto n = array.size();
std::vector<metadata_t> varr;
varr.reserve(n);
for (const auto &elem : array) {
// either:
varr.push_back(elem);
// or:
varr.emplace_back(elem);
// it doesn't really matter in this case, since they
// will both copy-construct the new element in the array
// from the current element being iterated...
}
return varr;
}
Which is really just a less-efficient way of avoiding the vector
's own copy constructor:
std::vector<metadata_t> consolidate(const std::vector<metadata_t> &array) {
return array; // will return a new copy of the array
}
The data structure does not have to be
unique_ptr<T[]>
. The old title referred tounique_ptr
but I am really looking for a solution for a fixed-size data structure. So far, it is the only data structure I found as a "constant-size indexed container with size defined at runtime".
What you are looking for is exactly what std::vector
already gives you. You just don't seem to realize it, or want to accept it. Both std::unique_ptr<T[]>
and std::vector<T>
hold a pointer to a dynamically-allocated array of a fixed size specified at runtime. It is just that std::vector
offers more functionality than std::unique_ptr<T[]>
does to manage that array (for instance, re-allocating the array to a different size). You don't have to use that extra functionality if you don't need it, but its base functionality will suit your needs just fine.
CodePudding user response:
Initializing an array of non-default constructibles from a vector
is tricky.
One way, if you know that your vector
will never contain more than a certain amount of elements, could be to create an index_sequence
covering all elements in the vector
. There will be one instantiation of the template for each number of elements in your vector that you plan to support and the compilation time will be "silly".
Here I've selected the limit 512. It must have a limit, or else the compiler will spin in endless recursion until it gives up or crashes.
namespace detail {
template <class T, size_t... I>
auto helper(const std::vector<T>& v, std::index_sequence<I...>) {
if constexpr (sizeof...(I) > 512) { // max 512 elements in the vector.
return std::unique_ptr<T[]>{}; // return empty unique_ptr or throw
} else {
// some shortcuts to limit the depth of the call stack:
if(sizeof...(I) 100 < v.size())
return helper(v, std::make_index_sequence<sizeof...(I) 101>{});
if(sizeof...(I) 50 < v.size())
return helper(v, std::make_index_sequence<sizeof...(I) 51>{});
if(sizeof...(I) 25 < v.size())
return helper(v, std::make_index_sequence<sizeof...(I) 26>{});
if(sizeof...(I) < v.size())
return helper(v, std::make_index_sequence<sizeof...(I) 1>{});
// sizeof...(I) == v.size(), create the pointer:
return std::unique_ptr<foo[]>(new T[sizeof...(I)]{v[I]...});
}
}
} // namespace detail
template <class T>
auto make_unique_from_vector(const std::vector<T>& v) {
return detail::helper(v, std::make_index_sequence<0>{});
}
You can then turn your vector
into a std::unique_ptr<metadata_t[]>
:
auto up = make_unique_from_vector(foos);
if(up) {
// all is good
}
CodePudding user response:
You have to allocate some uninitialized memory for an array and copy construct the elements in-place using construct_at
. You can then create a unique_ptr using the address of the constructed array:
#include <vector>
#include <memory>
struct metadata_t {
metadata_t(int) { }
};
typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;
fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
// note: this is run-time:
auto n = array.size();
std::allocator<metadata_t> alloc;
metadata_t *t = alloc.allocate(n);
for (std::size_t i = 0; i < array.size(); i) {
std::construct_at(&t[i], array[i]);
}
return fixedsize_metadata_t(t);
}