5:48 AM
0
A: Is it legal to initialize an array via a functor which takes the array itself as a parameter by reference?

DavislorAs an alternative, even if the element type is not default-constructible, you can still initialize a buffer of uninitialized memory, and create a std::array from that using std::to_array and a pointer cast. Be warned: The elements of the temporary array are not destructed after being moved from! ...

 
std::to_array from clang seems to have the same issue with std::index_sequence based methods and doesn't work for large N. Upvoted anyway.
 
@WeijunZhou I just compiled with an array of 500,000 Int, and the only problem I had was the compile time. The implementation limit might be a limitation of the Compiler Explorer sandbox, although it might also be a library issue (since I wasn’t on Linux) You could also try -stdlib=libstdc++.
 
For the first snippet, can we use a raw array as the buffer and reinterpret_cast it directly to std::array? I suppose it is well defined because std::array itself should have implicit lifetime. This avoids the issue whether the implementation uses std::index_sequence to implement to_array.
 
@WeijunZhou Yes; see my edit.
 
I could be wrong but I think the buffer may need to be raw array because implicit object creation does not cover the case of std::array<char, N> as the buffer
And if it is indeed valid for implicit objevt creation in a char array subobject instead of a complete object, then I'll just wrap the char array in some custom wrapper and make use of RAII by calling std::destroy in the destructor. It should then fix the issue you mentioned in the "Be Warned" part.
 
5:48 AM
@WeijunZhou Even if you consider “object” to be a magic word, std::uninitialized_move explicitly creates “objects,” so it’s fine. You might also create your buffer as an array of std::aligned_storage<Size, Alignment>::type, which is deprecated but does explicitly say it’s suitable to use as uninitialized storage for an object.
@WeijunZhou Thinking about it some more, it would probably be safer to use uninitialized_move_n and code defensively around a potential buffer overrun.
 
I agree. If we focus on generate_array(), you may also move the temp char array to the heap to avoid blowing up the stack.
 
@WeijunZhou Already have a version that does.
 
Oh you mean the vector version. I am not entirely sure you can reinterpret_cast part of an array of non-char type with a different size (determined by the capacity of the vector) like that, but that may be worth its own language-lawyer question. I think in practice it would work.
 
@WeijunZhou I don’t see anything in N4680 that says the reinterpret_cast<std::array<T,N>*>` is legal. An implementation of std::array`` would be able to add other arbitrary gunk at the beginning, or a checksum at the end, for example, so long as data() == addressof(front()). There would be more guarantees if we could move the data directly to result.data(), but that would require us to have a valid std::array, and we can’t default-construct one.
@WeijunZhou The std::vector should be exactly the same size, and we could test that at runtime, to be safe.
@WeijunZhou Made a number of fixes and added some assertions. Maybe my downvoter will even come back for another look.
 
I'm still not convinced you can reinterpret_cast a T* that you obtain from vector::data() to a pointer to T[N], let alone std::array<T,N>, given that: 1. It is not even legal to cast the first element of T[N] to the whole array (see this) and 2. Formally vector never really creates an array object in the first place. Like I said this is mostly language-lawyering. I may stick to using a new array expression with unsigned char type myself and use IOC.
 
5:48 AM
@WeijunZhou Language-lawyering: [vector.data] specifically says that [data(), data() + size()) is contiguous and “a valid range.” The [[iterator.concept.contiguous] requirement “provides a guarantee that the denoted elements are stored contiguously in memory,” An array also has contiguous storage, and [expr.add] guarantees that pointer and array arithmetic have the same layout. However, basic.compound says, “An array object and its first element are not pointer-interconvertible, even though they have the same address.” So, the reinterpret_cast is not guaranteed to work.
 
Yes, that's what I'm thinking. However using an unsigned char array buffer (either from the stack or the heap) and then reinterpret_casting to a std::array is guaranteed to work due to implicit object creation. So I guess that's a better way in terms of language-lawyering.
 
@WeijunZhou Which section of the Standard are you referring to? I don’t believe a std::array is required to have the same address as its first element, or an alignment restriction on its data that is no stronger. I do a static_assert to test for the first condition, but the second is a possible incompatibility, let’s say if every std::array aligns its data to a cache-line boundary and uses aligned load/store on an architecture that traps if those are misaligned.
 
I think you can use reinterpret_cast to start the lifetime of the std::array object (call it pArr), but not its individual elements, just like it is (explicitly) valid to start the lifetime of a raw array without starting the lifetime of individual elements. (This is something I'm not sure about and worth arguing with) Then it should be well-defined to access pArr->data() and start from there. The alignment issue can be handled by providing an alignas() when you allocate the unsigned char buffer.
 
@WeijunZhou Or a fat-pointer implementation might, at runtime, refuse to convert a pointer to an array unless the pointer has a size associated with it that says the conversion is safe from causing a buffer overrun. (Although the data() member of a contiguous container likely will, even then.)
 
That is a valid concern. However given that the array type is explicitly called out by the standard to be an implicit lifetime type I see no reason why std::array cannot behave the same. Like I said if that is valid I will use pArr->data() to loop for the elements, then it is no longer a concern whether pArr == pArr->data().
 
5:48 AM
@WeijunZhou I think we’re in agreement, then, that implementations are allowed to behave the way all major compilers do, and allow the cast to work, but they could also break it, and I can even think of some sane reasons to. Another thing i could be doing is aligning the buffer to at least the alignment of a std::array<T,N>, if greater than that of T.
 
Mostly. What I meant was to change reinterpret_cast<Int*>(scratch.data()) to reinterpret_cast<std::array<Int,N>*>(scratch.data())->data() in the first variant (sorry about the abomination). As for the alignment, yes you can try to improve on that and see if it does anything about the downvote.
 
@WeijunZhou It would be nice to get a brief explanation of the downvote, yes.