7

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.

13
  • the #define is a red herring, the real problem is that Bar has no default constructor Commented Aug 21, 2017 at 14:05
  • Making a Bar without its containing Foo instance also makes no sense. Of course I can always change Bar::Foo& into Bar::Foo* and create the link at a later time (that is, in the body of Foo::Foo() after the initializer list) but I want to explore other options first. Commented Aug 21, 2017 at 14:10
  • A Bar that contains a reference to a Foo that contains an array of Bars that contains a reference to a Foo ... ? When will this end? Commented Aug 21, 2017 at 14:11
  • A Foo consists of Bars and a Bar knows its parent Foo. Commented Aug 21, 2017 at 14:12
  • 2
    @manni66 I suspect it ends right there. A Foo with an array of Bars which each have a reference to that Foo. Commented Aug 21, 2017 at 14:12

2 Answers 2

5

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).

Sign up to request clarification or add additional context in comments.

Comments

1

The solution is to build a char array (or a byte array in C++ 17) and use a pointer from there to have a Bar array. An union with a single Bar is enough to ensure proper alignment:

#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 the X are constructed in place, everything is acceptable for any C++11 compiler and you get a true random access container. There is no Undefined Behaviour, because the memory was reserved by the char array, and the objects are correctly constructed there. This is a generic trick to delay a complex member initialization inside the constructor with the full power of the language, when thing become hard to do with a mere initializer or with compile time meta-programming.


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

You could try to build a custom recursive array, to allow initialization of all of its elements from the same 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 it works as per your requirements but is pretty useless as is because all the Bar elements refere to the same Foo. It will only make sense when Bar class will contain other members.

4 Comments

A very creative solution! Memory layout also seems alright. The only disadvantage of this approach is that RArr isn't a random-access container anymore. The time complexity of element access went from O(1) to O(n). It resembles a singly linked list but without its advantages (and without some disadvantages as well like horrible cache coherency). This might be solved with some nasty memory management tricks (casting an RArr to an X array and indexing into that array for example). Better way could be putting an RArr into a union together with an X array. I'll try that now.
@Jupiter: Your idea of union to have a true random-access container lead me to the right path. The correct solution is much simpler!
I'm not sure if I think that messing around with new and aliasing char arrays to be an improvement over template metaprogramming...
@NirFriedman: I agree that your solution is nicer than my rather C-ish one... It is an old trick now with all the meta-programming features of recent standards, but I prefere to leave it here for readers forced to use older compilers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.