Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing non-static member array of size defined in a #define of 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 should I initialize Foo::myBars? The number of Bars is known at compile time but I just want to specify this number in one place (in the define preferably, other suggestions are welcome).

Usage of Foo::mybars:

  • Size of it will never change during runtime but I haven't decided what the number will be yet but will be somewhere in the range [1..10] (or so) (number might change a lot during development).
  • Element values will never change (always will be the same non-const objects).
  • Relation ship between Foo and Bar is that of composition. A Foo is made out of Bars and their lifetime is the same. Bar without a Foo and visa versa makes no sense.
  • The Foo and Bar classes are part of performance critical code and their size will influence the memory footprint of the program a lot (80-90% of the memory footprint will be Foos and Bars).

Compiler: MSVS2017 version 15.3

Edit: Changing std::array<Bar, BARS_IN_FOO> myBars; to Bar myBars[BARS_IN_FOO]; is also oke if this helps.

Important edit: All the ctors of Bar and Foo are public. This was a mistake. I changed this.

like image 391
Jupiter Avatar asked Aug 21 '17 14:08

Jupiter


1 Answers

You can do this pretty easily if you can assume that Bar is movable/copyable:

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 could easily refactored to use a more generic template metaprogramming utility of "repetition". I suspect that any template meta programming library will have some such utility, here I don't bother factoring it out since it is a one liner anyhow. But if you come across such problems often it's something to consider (just write a function that returns an N-entry initializer list all with the same expression).

Live example: http://coliru.stacked-crooked.com/a/aab004c0090cc144. Sorry couldn't easily access an MSVC compiler. Also compiled with 14 since I didn't need any 17 features.

Edit: even if Bar is not movable/copyable, or if you don't think things will get optimized, this technique can actually be modified to work even in that case. You can generate an array<std::reference_wrapper<Foo>, N instead. But this is a little bit more complex and typically in C++ most things should be movable, and usually constructor calls are not in the critical path. Still I can elaborate if necessary.

Edit2: also, please don't use a #define for that. constexpr static auto BARS_IN_FOO = 5; should work exactly the same way, except that it is properly namespaced (and probably some other macro nastiness I'm forgetting).

like image 93
Nir Friedman Avatar answered Dec 22 '22 06:12

Nir Friedman