I just ran across some unexpected and frustrating behaviour while working on a C++ project. My actual code is a tad more complicated, but the following example captures it just
Tried just creating the array with a size? Default ctor should be called.
As in this source
struct foo {
int x;
foo():x(1) {}
private:
foo( foo const& ) {}
};
foo array[10];
#include <iostream>
int main() {
for (auto&& i:array) {
std::cout << i.x << "\n";
}
}
which demonstrates initialized foo
in an array with no default copy constructor.
If your problem is that you actually want to construct the foo
with a non-default constructor, this can be done as well, but it is much harder, and that isn't what your question asked. In any case, here is a very, very rough sketch of the kind of stuff needed to create an array-like structure that supports emplaced construction of its elements. It is far from finished or compiling, but the basic technique should be sound:
#include <cstddef>
#include <utility>
#include <type_traits>
template<typename... Args>
struct types {};
template<typename types, typename=void>
struct emplacer;
template<typename T>
struct remove_refref {
typedef T type;
};
template<typename T>
struct remove_refref<T&&> {
typedef T type;
};
template<typename A1, typename... Args>
struct emplacer< types<A1, Args...>, void>:
emplacer< types<Args...> >
{
typename remove_refref<A1>::type val;
emplacer( A1 arg, Args... args ):
emplacer< types<Args...>, index+1 >( std::forward(args)... ),
val( std::forward(arg) )
{}
};
template< std::size_t n >
struct extract {
template< typename A1, typename... Args >
A1&& from( emplacer<types<A1, Args...>&& e ) {
return extract<n-1>::from( emplacer<types<Args...>>&&(e) );
}
};
template<>
struct extract<0> {
template< typename A1, typename... Args >
A1&& from( emplacer<types<A1, Args...>&& e ) {
return std::move( e.val );
}
};
template<std::size_t... v>
struct seq {};
template<std::size_t n, std::size_t... tail>
struct make_seq: make_seq<n-1, n-1, tail...> {};
template<std::size_t n, std::size_t... tail>
struct make_seq<0, tail...> {
typedef seq<tail...> type;
type val() { return type(); }
};
struct nothing {};
template<typename T, typename... Args, std::size_t... indexes>
nothing construct( T* src, emplacer<types<Args...>>&& e, seq<indexes...> s = make_seq< sizeof...(Args) >::val() ) {
new(src)T( std::move( extract<indexes>( std::move(e) ))... );
}
template<typename... Args>
emplacer< types<Args...> > emplace( Args&&... a ) {
return emplacer< types<Args...> >( std::forward(a)... );
}
template<typename T, std::size_t n>
struct my_array {
private:
union T_mem {
T t;
char x;
T_mem():x(0) {}
};
T_mem buff[n];
template<typename... nothings>
void do_nothing( nothings...&& ) {}
template<typename... emplacers, std::size_t... indexes>
my_array( emplacers&&... em, seq<indexes...> s=make_seq< sizeof...(emplacers) >::val() ) {
do_nothing( construct( &buff[indexes].t, em)... );
}
~my_array() {
for( auto&& v:buff) {
v.t.~T();
}
}
T& operator[](std::size_t n) { return buff[n].t; }
// etc
};
The idea is that we create an array like construct that is actually an array of union
to both T
and a char
. We thus avoid actually constructing our T
, while still having proper alignment and such for one. Assuming char
has no non-trivial alignment, the resulting buffer is binary-compatible with a T[]
.
We then take as a construction argument emplacer
objects, which act as packages for arbitrary construction arguments. For annoying reasons, these objects need to create a temporary copy of some of their parameters (I cannot figure out how to avoid the lifetime issues... maybe I'm missing something).
The constructor of the my_array
takes any number of emplacers
and proceeds to construct the contents of buff
based on their arguments.
You'd create your array something like this:
my_array< Foo, 10 > arr = {
emplacer( a, b, c ),
emplacer( x, y, z ),
...
};
a bit more work would allow default construction of uninitialized objects in the array.
But this is really, really tricky to write correctly.
Because individual array elements are initialized by copy-initialization from the initializers specified through = {...}
syntax. See 8.5/12 (C++03)
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization
Copy-initialization requires copy constructor (even if it won't actually use it).
In practice, if you make your code compile by making the copy constructor public, the compiler will probably end up initializing your array elements in place, without using the copy constructor. Nevertheless, the formal rules of abstract language call for copy-initialization in this context.
class Irritating
{
public: Irritating() {}
private: Irritating(const Irritating& other) {}
};
enum DefaultConstruct { defaultConstruct };
class MaybeTooClever
: public Irritating
{
public:
MaybeTooClever( DefaultConstruct = defaultConstruct ) {}
#ifdef __GNUC__
public:
MaybeTooClever( MaybeTooClever const& other ); // No such.
#else
private:
MaybeTooClever( MaybeTooClever const& other ); // No such.
#endif
};
static MaybeTooClever const array[] = { defaultConstruct };
int main()
{}
Assuming the copy constructor of Irritating
is disabled because it is expensive than perhaps it is best to manage them by reference:
vector<unique_ptr<Irritating>> V = { new Irritating(), ... };
You could use shared_ptr
instead of unique_ptr
depending on the usage pattern.
(If you could modify Irritating you could give it a move constructor, take a look at move semantics)
If you really want them constructed in place than you could use aligned_storage
to make an array of storage for them and then placement new them in place. This would produce almost identical compiled code to what you want to do with your original request, but it is a little messier:
aligned_storage <sizeof(Irritating), alignment_of<Irritating>::value>::type data[N];
new ((Irritating*) data+0) Irritating(...);
new ((Irritating*) data+1) Irritating(...);
new ((Irritating*) data+2) Irritating(...);
...
new ((Irritating*) data+N-1) Irritating(...);
(Dont forget to placement delete them at program exit.)