random
random

Reputation: 4038

Split constructor code in class body

Is it possible in C++ somehow split constructor into multiple pieces in class body? Like in Scala or Kotlin? I need it for DSL written using C++ metaprogramming.

I have data members with non-trivial initialization sequence, and I want to specify initialization code together with members declaration.

Today I have:

class foo {
    foo() {
       // code to initialize a;
       a.something();  
       a.other_thing()
       // code to initialize b;
       b.other_thing(a);             
    }

    T a;     
    T b; 
};

Instead I want to specify initialization code together with member declaration, something like:

class foo {
    T a; 
    {
       a.something();  
       a.other_thing()
    }  

    T b; 
    {
       b.other_thing(a);             
    } 
};

I've found two solutions that I don't like. First is to pass std::function parameter to every type, so I can initialize data members like this:

class foo {
    a{ [&](){
       // initialization code
    }};
};

Unfortunately this requires refactoring of all third-party libraries I use.

Second option is introducing a "dummy" data member just to execute lambda:

T a;
dummy_type a_init = [](&) { ... }

That can be hidden inside MACRO:

T a; INIT(a) { ... }

Everything is nice, but how can I create a zero-sized object in C++, so that each dummy initializer does not increase class size?

Upvotes: 0

Views: 447

Answers (1)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385274

You can use a lambda without any external changes if you call it immediately:

class foo
{
    foo()
    : a([=]() {
        T a;
        a.something();
        a.other_thing();
        return a;
    }())
    , b([=]() {
        T b;
        b.other_thing(a);
        return b;
    }())
    {}

    T a;     
    T b; 
};

Is it pretty? Hell no! But it should work, unless T is hard/impossible to move/copy.

(live demo)

You can move the initialisers inline, too:

class foo
{
    T a = [=]() {
        T a;
        a.something();
        a.other_thing();
        return a;
    }();

    T b = [=]() {
        T b;
        b.other_thing(a);
        return b;
    }(); 
};

(live demo)

Alternatively set up a factory function/type somewhere and just invoke that instead.

If you're really desperate, make the members unique_ptrs and resort to dynamic allocation like in the olden days (which you can scatter throughout your constructor body however you like). Probably the easiest solution tbh.

class foo
{
public:
    foo()
    {
        a = std::make_unique<T>();
        a->something();
        a->other_thing();

        b = std::make_unique<T>();
        b->other_thing(*a);
    }

private:
    std::unique_ptr<T> a;     
    std::unique_ptr<T> b; 
};

(live demo)

Upvotes: 3

Related Questions