How I can keep aggregate initialization while also adding custom constructors?
Asked Answered
D

4

33

If I don't define a constructor in a struct, I can initialize it by just picking a certain value like this:

struct Foo {
    int x, y;
};

Foo foo = {.y = 1};

But if I add new default constructor then I lose this feature:

struct Bar {
    int x, y;
    Bar(int value) : x(value), y(value) {}
};

Bar bar1 = 1;
Bar bar2 = {.y = 2}; // error: a designator cannot be used with a non-aggregate type "Bar"

Is it possible to have both ways?

I tried adding the default constructor Bar () {} but it seems to not work either.

Dorelle answered 17/7, 2022 at 14:43 Comment(5)
Have you tried = default instead of {} Christeenchristel
Once you add a non-default constructor, your structure is no longer an aggregate (like a plain old C-like structure, or an array). You can have one or the other, but not both.Pasteurism
@Christeenchristel it seems like this doesn't work either Bar() = default;Dorelle
@Someprogrammerdude Oh I see, thank you. The closest I got was like this Bar(Vec2 value) : x(value.x), y(value.y){}; but then I have to use double parens like this Bar bar2 = {{.y = 2}}Dorelle
@Christeenchristel He want's aggregate initialization and not a default constructor. The question is misleading and should be edited. I don't believe there is a way to reinstate the aggregate constructor once you have a constructor. Could be a nice C++ extension though.Gride
G
8

Similar to what ellipticaldoor wrote:

struct FooBase {
    int x = 0, y = 0;
};

struct Foo : FooBase {
    Foo(int x_) : FooBase{.x = x_} { }
    Foo(FooBase &&t) : FooBase{t} {}
};

Foo foo = {{.y = 1}};
Foo foo2{1};
Gride answered 17/7, 2022 at 16:1 Comment(0)
D
34

You can't have your cake and eat it too. If the object has a constructor it is no longer an aggregate, and only aggregates can be initialized with designated initializers. You can't use constructors for arbitrary initialization logic with aggregates.

Are we toasted though? No, because there's the "named constructor" idiom. It's essentially just a static member function that returns an initialized object, and able to perform some logic. The idiom is compatible with aggregate initialization.

struct Foo {
    int x, y;
    static Foo filled_with(int value) {
        return {.x = value, .y = value};
    }
};

Foo foo = {.y = 1}; // Still an aggregate.
Foo foo2 = Foo::filled_with(2); // Custom logic

There's not even any copying or moving with this approach, because C++17 removed the possibility for those. foo2 is initialized directly with whatever the static member does.

Dorinda answered 17/7, 2022 at 15:7 Comment(0)
G
8

Similar to what ellipticaldoor wrote:

struct FooBase {
    int x = 0, y = 0;
};

struct Foo : FooBase {
    Foo(int x_) : FooBase{.x = x_} { }
    Foo(FooBase &&t) : FooBase{t} {}
};

Foo foo = {{.y = 1}};
Foo foo2{1};
Gride answered 17/7, 2022 at 16:1 Comment(0)
D
3

So far this is the closest thing I can find:

struct Vec2 {
    int x, y;
};

struct Bar {
    int x, y;
    Bar(int value) : x(value), y(value) {}
    Bar(Vec2 value) : x(value.x), y(value.y){};
};

Bar bar1 = 1;
Bar bar2 = {{.y = 2}};

But you need to use double params

Dorelle answered 17/7, 2022 at 14:59 Comment(1)
This is the same solution that I came up with. I like StoryTeller's solution better.Weksler
G
2

You can use a data member initializer instead so the type remains an aggregate:

struct Foo {
    int x = 0, y = x;
};

Foo foo1 = {.y = 6};  // Foo{0, 6}
Foo foo2{7};          // Foo{7, 7}

(Though it can't be implicitly constructed from int)

Gwynethgwynne answered 17/7, 2022 at 17:36 Comment(2)
oh, this interesting, but the issue will be this though Foo foo1 = {.x = 6}; // Foo{6, 6} I will like in this case for y to stay at zeroDorelle
@Dorelle There would be no difference between {.x = 6} and {6} if that is aggregate initialization of Foo, so you would have to do something like the other answersGwynethgwynne

© 2022 - 2024 — McMap. All rights reserved.