Initializing a non-static array of size elements defined in #define class type without default ctor

I have the following class:

//in some .h file #define BARS_IN_FOO 5 //The only place where this number should be specified. //All code should work when I change this //in some .cpp file struct Foo; struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} } //Cannot be default initialized struct Foo { std::array<Bar, BARS_IN_FOO> myBars; Foo() : myBars{ } //Error. Cannot default initialize Bars. //I want to initialize all Bars with Bar{*this} but at this point I don't //know how many Bar initializers I should put in the myBar initializer list {} } 

So how do I initialize Foo::myBars ? The Bar number is known at compile time, but I just want to specify this number in one place (in the definition, preferably, other suggestions are welcome).

Using Foo::myBars :

  • The file size will never change at run time, but I have not decided what number it will be, but it will be somewhere in the range [1..10] (or so) (the number can change a lot during development).
  • The values โ€‹โ€‹of the elements will never change (there will always be the same non-constant objects).
  • The ratio between Foo and Bar is the composition ratio. A Foo made up of Bar and their lifespan is the same. Bar without Foo and vice versa makes no sense.
  • The Foo and Bar classes are part of the critical performance code, and their size will greatly affect the program memory (80-90% of the memory will be Foo and Bar s).

Compiler: MSVS2017 version 15.3

Edit: change std::array<Bar, BARS_IN_FOO> myBars; on Bar myBars[BARS_IN_FOO]; also equal oke if that helps.

Important editing: all ctors Bar and Foo are publicly available. It was a mistake. I changed that.

+7
c ++ arrays constructor
source share
2 answers

You can do this quite easily if you can assume that Bar is movable / copied:

 template <std::size_t ... Is> std::array<Bar, sizeof...(Is)> make_bar_array_impl(Foo& f, std::index_sequence<Is...>) { return { (Is, f)... }; } template <std::size_t N> std::array<Bar, N> make_bar_array(Foo& f) { return make_bar_array_impl(f, std::make_index_sequence<N>{}); } Foo() : myBars(make_bar_array<BARS_IN_FOO>(*this)) {} 

This can be easily reorganized to use the more versatile metaprogramming utility for repeating patterns. I suspect that in any library of template metaprograms there will be some kind of utility, here I do not bother with factoring, since this is one liner. But if you encounter such problems, often this needs to be taken into account (just write a function that returns a list of N-record initializers, all with the same expression).

Real-time example: http://coliru.stacked-crooked.com/a/aab004c0090cc144 . Unfortunately, it was not possible to access the MSVC compiler. Also compiled with 14, since I don't need any 17 functions.

Edit: even if Bar cannot be moved / copied, or if you do not think that everything will be optimized, this method can actually be changed to work even in this case. Instead, you can create an array<std::reference_wrapper<Foo>, N But this is a little more complicated and usually in C ++ most things should be movable, and usually the constructor calls are not in a critical way. However, I can clarify if necessary.

Edit2: also please do not use #define for this. constexpr static auto BARS_IN_FOO = 5; should work in exactly the same way, except that it is correctly placed in the names (and possibly some other macro-boxes that I forget).

+5
source share

The solution is to build a char array (or a byte array in C ++ 17) and use a pointer from there to have an array of bars. To ensure proper alignment, a connection with one bar is sufficient:

 #include <iostream> #define BARS_IN_FOO 5 // X is the type of the "array", Y the type of its initializer, n the size template<class X, class Y = X, int n = 1> class InitArr { union { X initial; // guarantee correct alignment char buffer[n * sizeof(X)]; // only to reserve enough place }; public: InitArr(Y& inival): initial(inival) { for (int i = 0; i < n; i++) { new(&initial + i)X(inival); // properly construct an X at &initial +i // which is guaranteed to be inside buffer } } X& operator[] (int i) { // make InitArr behave like an array X* arr = &initial; return arr[i]; // could add control that 0 <= i < n } }; struct Foo; struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} }; //Cannot be default initialized struct Foo { InitArr<Bar, Foo, BARS_IN_FOO> myBars; Foo() : myBars{ *this } //I want to initialize all Bars with Bar{*this} {} }; int main() { Foo foo; std::cout << &foo << std::endl; // shows that all foo.myBars[i] point to the original foo for (int i = 0; i < BARS_IN_FOO; i++) { std::cout << &(foo.myBars[i].foo) << std::endl; } return 0; } 

As X builds in place, everything is acceptable for any C ++ 11 compiler, and you get a real random access container. There is no Undefined Behavior, because the memory is reserved by the char array, and the objects there are correctly constructed. This is a common trick to delay complex member initialization within the constructor with the full power of the language, when a thing becomes difficult to do with a simple initializer or with a compile-time metaprogram.


Here is my original idea, left here for reference, but really less pleasant ...

You can try to create a custom recursive array to allow the initialization of all its elements from a single value:

 // X is the type of the "array", Y the type of its initializer, n the size template<class X, class Y=X, int n = 1> class RArr { public: X first; RArr<X, Y, n-1> other; RArr(Y& i) : first(i), other(i) {} X& operator [] (int i) { if (i >= n || i < 0) throw std::domain_error("Out of range"); if (i == 0) return first; return other[i - 1]; } }; // specialization for n=1 template <class X, class Y> class RArr<X, Y, 1> { public: X first; RArr(Y& i) : first(i) {} X& operator [] (int i) { if (i != 0) throw std::domain_error("Out of range"); return first; } }; struct Foo; struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} }; //Cannot be default initialized struct Foo { RArr<Bar, Foo, BARS_IN_FOO> myBars; Foo() : myBars{ *this } //I want to initialize all Bars with Bar{*this} {} }; int main() { Foo foo; std::cout << &foo << std::endl; // shows that all foo.myBars[i] point to the original foo for (int i = 0; i < BARS_IN_FOO; i++) { std::cout << &(foo.myBars[i].foo) << std::endl; } return 0; } 

Well, this works according to your requirements, but it is pretty useless, since all Bar elements reference the same Foo . This will only make sense when the Bar class contains other members.

+1
source share

All Articles