C++ Structure Initialization [duplicate]
Asked Answered
C

17

379

Is it possible to initialize structs in C++ as indicated below:

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};

address temp_address = { .city = "Hamilton", .prov = "Ontario" };

The links here and here mention that it is possible to use this style only in C. If so why is this not possible in C++? Is there any underlying technical reason why it is not implemented in C++, or is it bad practice to use this style. I like using this way of initializing because my struct is big and this style gives me clear readability of what value is assigned to which member.

Please share with me if there are other ways through which we can achieve the same readability.

I have referred the following links before posting this question:

  1. C/C++ for AIX
  2. C Structure Initialization with Variable
  3. Static structure initialization with tags in C++
  4. C++11 Proper Structure Initialization
Civilian answered 17/7, 2012 at 5:46 Comment(17)
Personal view of the world: you don't need this style of object initialization in C++ because you should be using a constructor instead.Granuloma
Yes I thought of that, but I have an array of big Structure. It would be easy and readable for me to use this way. Do you have any style/good practice of initializing using Constructor which gives better readability too.Civilian
Have you considered the boost parameter library, in combination with constructors? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.htmlKalinda
Not so programming related: this address works fine in the US only. In France, we don't have an "province", in other parts of the world, there is no postal code, a grand-mother of a friend lives in such a small village that her address is "Ms X, postal-code small-village-name" (yep, no street). So consider carefully what a valid address is to the market you will apply this to ;)Praise
@MatthieuM. There are no provinces in the US (this might be a Canadian format?), but there are states, territories, and even tiny villages that don't bother to name streets. So the issue of address conformance applies even here.Emanuel
this isn't as user friendly as in a language such as C# where you can easily map exposed members without worrying about ordering. If your code relies on the fact that one field is defined before another in something that is supposed to promote abstraction (OOP), then it is a failed design style IMO and should never be used.Personable
I had this issue too and I ended up with using the C-Style initalizer by putting the intializer in a separate .c file which is at aleast in MSVC compiled with the C parser by default. I really needed this because the C++ unnamed initializer syntax is just wrong. I had to init a union with bitfields where it is not clear at all which value is initialized when using the unnamed C++ initializer syntax.Poor
@PhilipKendall As soon as you add a constructor to a struct it ceases to be a plain-old-type. This has some consequences, for example a struct like that cannot be a part of an union.Bloodshot
@PhilipKendall But Object constructors don't allow for keyword-style initialization which is arguably more readable: Circle c{.radius = 5} Circle c(5) C99 style tells you what the argument is You can use optional parameters in C++ and always provide them, but that's not as flexible (requires you to know the order of parameters when using the class, rather than just remembering the names).Krefetz
Not yet it was purposefully left out of c++11. But this feature will be available in c++20. open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0329r0.pdfFracas
@PhilipKendall That's fine...if the structure is defined within C++. If it was defined in a C header, being used as extern "C", you don't get to have a constructor, and you need some form of aggregate initialiser.Boswell
@Jason Liam Why did you mark this popular 2012 question as duplicate of a 2019 question with low attention?Abagael
@Abagael Because it solves and directly address OP's problem.Yoshi
@Jason Liam It provides a solution for the latest version of the standard only. This is not applicable for many users (me included). There are big fractions of users which stuck on older compilers.Abagael
@Abagael Ofcourse, now that the questions are linked to each other, the new users can see both of these posts and choose solutions given in these two questions according to their needs(like which version they're using etc).Yoshi
@Jason Liam I might not know what is common practice on stackoverflow. Still I think the choice would be really questionable in academics. I would rather extend and upvote the answer of sameer chaudhari (which is older than the linked "original" question) than tagging this question as duplicate (which it isn't in a academic sense).Abagael
@Philip Kendall There are many use cases for C++ that don't involve running on a personal computer. In an embedded environment, one might want to store a large array of structs directly in flash memory (register init data for example) and never bring it to RAM at all (because there might not be enough, or no use for it here). In that case I don't see how a constructor could even run since objects are not being constructed into memory.Shend
V
238

If you want to make it clear what each initializer value is, just split it up on multiple lines, with a comment on each:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Verbenaceous answered 17/7, 2012 at 6:3 Comment(11)
I personally like and recommend this styleCivilian
Beware of initializing one member with an expression using other members, lest values be used before being set (here, to 640 and 480): foo = { new char[foo.width * foo.height], 640, 480 };Walley
What is the difference between doing that, and actually using dot notation to access MORE ACCURATELY the field itself, its not like you are saving any space if that's what the concern is. I really don't get C++ programmers when it comes to being consistent and writing maintainable code, they seem to always want to do something different to make their code stand out, the code is meant to reflect the problem being solved it shouldn't be an idiom on its own, aim for reliability and ease of maintenance.Personable
@Personable well, for one, in this case the order in which you put your members is of upmost importance. If you add a field in the middle of your structure, you will have to go back to this code and look for the exact spot in which to insert your new initialization, which is hard and boring. With the dot notation, you can simply put your new initialization at the end of the list without bothering with the order. And dot notation is way safer if you happen to add the same type (like char*) as one of the other members above or below in the structure, because there's no risk of swapping them.Maryjomaryl
orip's comment. If the data structure definition gets changed, and nobody thinks to look for the initializations, or can't find them all, or makes a mistake editing them, things will fall apart.Flatcar
Most (if not all) POSIX structs don't have a defined order, only defined members. (struct timeval){ .seconds = 0, .microseconds = 100 } will always be a hundred microsecond, but timeval { 0, 100 } might be a hundred SECONDS. You don't want to find something like that out the hard way.Iodide
Comments won't change when you rename fields, this is a bad idea.Bernstein
Just a reference, this is called "Aggregate initialization".Nelsonnema
The compiler doesn't read comments and that's why this style is a bad idea.Puffery
I cannot recommend this style. I just struggled for 2 hours because of an incorrect order during initialization of a struct until I was able to pinpoint the source. Even if there was any performance advantage (none that I've heard) and it does involve writing more code, in terms of being more readable and less prone to errors the dot notation is far superior not to mention the huge mess that arises when you add a new field to the structure. The resulting code refactoring is probably going to diminish any profit of "typing faster" the initialization suggested in this answer.Cartilage
It also does not address any of the why questions asked by the OP.Bonhomie
M
133

After my question resulted in no satisfying result (because C++ doesn't implement tag-based init for structures), I took the trick I found here: Are members of a C++ struct initialized to 0 by default?

For you it would amount to do that:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

This is certainly the closest to what you wanted originally (zero all the fields except those you want to initialize).

Maryjomaryl answered 17/7, 2012 at 6:22 Comment(8)
This does not work for statically inintialized objectsCruck
static address temp_address = {}; will work. Filling it up afterwards is up to the runtime, yes. You can bypass this by providing a static function that does the init for you: static address temp_address = init_my_temp_address();.Maryjomaryl
In C++11, init_my_temp_address can be a lambda function: static address temp_address = [] () { /* initialization code */ }();Figure
Bad idea, it violates the RAII principle.Columbuscolumbyne
Really bad idea: add one member to your address and you'll never know of all the places that create an address and now do not initialize your new member.Sequestration
@Sequestration I might be wrong but I don't think dot-initialization would raise a warning if you don't fill all fields...Ileanaileane
I was referring to the answer provided here: if you allow your objects to be default-built, and then you set all members, you will very quickly end up with old code having partially-initialized objects, while new code expects fully-initialized ones. By having a proper constructor, you will get a compile error when you add a parameter to the constructor, which is a good thing, as it will force you to revise all places where it was initialized before, and you get a chance to review how to handle the situationSequestration
Another bad reason: in DEBUG mode on most compilers you may find that your uninitialized members are initialized to non-zero "garbage" data, as opposed to default values per type that you would expect, and see in RELEASE mode.Checkmate
D
62

As others have mentioned this is designated initializer.

This feature is part of C++20

Deictic answered 4/12, 2017 at 16:16 Comment(3)
More information here : en.cppreference.com/w/cpp/language/aggregate_initializationGigi
No, it's from c++11 other than c++20!Squiggle
@Squiggle not according to cppreference.Ileanaileane
P
26

The field identifiers are indeed C initializer syntax. In C++ just give the values in the correct order without the field names. Unfortunately this means you need to give them all (actually you can omit trailing zero-valued fields and the result will be the same):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Petiole answered 17/7, 2012 at 5:51 Comment(9)
Yes you can always use aligned struct initialization.Satrap
Yes, currently I am using this method only(Aligned Struct Initialization). But I feel the readability is not good. Since my Structure is big the initializer has so many data and it is difficult for me to track which value is assigned to which member.Civilian
@DineshP.R. Then write a constructor!Argumentation
@MrLister (or anyone) Perhaps I'm stuck in a cloud of stupidity at the moment, but care to explain how a constructor would be much better? Seems to me there's little difference between providing a bunch of order-dependent unnamed values to an initializer list or providing a bunch of order-dependent unnamed values to a constructor... ?Angiology
unless you're talking about default values,, in which case I understand.Angiology
@Angiology To be honest, I don't really recall why I thought a constructor would be the answer to the problem. If I remember, I'll come back to you.Argumentation
@MrLister Oh no worries .. getting close to 6 years ago! I'm late to the party, thanks for getting back with me.Angiology
@DineshP.R. Sounds like the real problem is that your class is too big. Split it up into meaningful little bits.Ponder
A constructor may be the better option for IDE tipsCyanohydrin
I
22

This feature is called designated initializers. It is an addition to the C99 standard. However, this feature was left out of the C++11. According to The C++ Programming Language, 4th edition, Section 44.3.3.2 (C Features Not Adopted by C++):

A few additions to C99 (compared with C89) were deliberately not adopted in C++:

[1] Variable-length arrays (VLAs); use vector or some form of dynamic array

[2] Designated initializers; use constructors

The C99 grammar has the designated initializers [See ISO/IEC 9899:2011, N1570 Committee Draft - April 12, 2011]

6.7.9 Initialization

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

On the other hand, the C++11 does not have the designated initializers [See ISO/IEC 14882:2011, N3690 Committee Draft - May 15, 2013]

8.5 Initializers

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

In order to achieve the same effect, use constructors or initializer lists:

Infare answered 30/5, 2017 at 18:2 Comment(1)
Should be accepted answer as it actually addresses questions the OP asked.Bonhomie
T
14

I know this question is quite old, but I found another way of initializing, using constexpr and currying:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member, member3}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

This method also works for global static variables and even constexpr ones. The only disadvantage is the bad maintainability: Everytime another member has to be made initializable using this method, all member initialization methods have to be changed.

Tarton answered 3/10, 2014 at 23:56 Comment(2)
This is the builder pattern. The member methods can return a reference to the property to be modified instead of creating a new struct every timeScorpio
@Scorpio Actually, if @Tarton did that, they wouldn't be able to do more than one call as they did in the usage example. However, they could only change the value and return *this; as a reference instead if they don't use constexpr. That would result in the same usage pattern and avoid reconstructing a new object every time.Predate
B
10

You can just initialize via a constructor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
Beeman answered 14/2, 2014 at 4:51 Comment(1)
This is the case only if you control the definition of struct address. Also, POD types often intentionally have no constructor and destructor.Latvina
H
10

I might be missing something here, by why not:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}
Haunch answered 7/9, 2018 at 10:20 Comment(3)
I compiled the above program with a MinGW C++ compiler, and an Arduino AVR C++ compiler, and both ran as expected. Notice the #include <cstdio>Haunch
@run_the_race, this is about what the c++ standard says not what a given compiler's behavior may be. However, this feature is coming in c++20.Fracas
this only works if the struct is POD. So it will stop compiling if you add a constructor to it.Falsetto
E
7

You can even pack Gui13's solution into single initialization statement:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Disclaimer: I don't recommend this style

Extravagance answered 17/7, 2012 at 9:46 Comment(1)
This is still dangerous because it allows you to add a member to address and the code will still compile with a million places only initializing the original five members. The best part of struct initialization is that you can have all members const and it will force you to initialize them allSequestration
L
4

It's not implemented in C++. (also, char* strings? I hope not).

Usually if you have so many parameters it is a fairly serious code smell. But instead, why not simply value-initialize the struct and then assign each member?

Leeds answered 17/7, 2012 at 6:2 Comment(4)
"(also, char* strings? I hope not)." - Well, it is a C example.Austral
cant we use char* in C++? Currently I am using it and it is working (may be I am doing something wrong). My assumption is that the compiler will create constant strings of "Hamilton" & "Ontario" and assign their address to the struct members. Will it be correct to use const char* instead?Civilian
You can use char* but const char* is much more type-safe and everybody just uses std::string because it's much more reliable.Leeds
Ok. When I read "as mentioned below" I assumed it was an example copied from somewhere.Austral
Q
4

In C++ the C-style initializers were replaced by constructors which by compile time can ensure that only valid initializations are performed (i.e. after initialization the object members are consistent).

It is a good practice, but sometimes a pre-initialization is handy, like in your example. OOP solves this by abstract classes or creational design patterns.

In my opinion, using this secure way kills the simplicity and sometimes the security trade-off might be too expensive, since simple code does not need sophisticated design to stay maintainable.

As an alternative solution, I suggest to define macros using lambdas to simplify the initialization to look almost like C-style:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

The ADDRESS macro expands to

[] { address _={}; /* definition... */ ; return _; }()

which creates and calls the lambda. Macro parameters are also comma separated, so you need to put the initializer into brackets and call like

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

You could also write generalized macro initializer

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

but then the call is slightly less beautiful

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

however you can define the ADDRESS macro using general INIT macro easily

#define ADDRESS(x) INIT(address,x)
Quintus answered 6/7, 2016 at 11:16 Comment(1)
That is truly hideous, but you are one of the few that has read the OPs actual questionBonhomie
V
4

I found this way of doing it for global variables, that does not require to modify the original structure definition :

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

then declare the variable of a new type inherited from the original struct type and use the constructor for fields initialisation :

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

Not quite as elegant as the C style though ...

For a local variable it requires an additional memset(this, 0, sizeof(*this)) at the beginning of the constructor, so it's clearly not worse it and @gui13 's answer is more appropriate.

(Note that 'temp_address' is a variable of type 'temp_address', however this new type inherit from 'address' and can be used in every place where 'address' is expected, so it's OK.)

Vasta answered 7/11, 2016 at 10:18 Comment(0)
I
4

Inspired by this really neat answer: (https://mcmap.net/q/88235/-why-does-c-11-not-support-designated-initializer-lists-as-c99-closed)

You can do lamba closures:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

Or, if you want to be very fancy

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

There are some drawbacks involved with this, mostly having to do with uninitialized members. From what the linked answers comments say, it compiles efficiently, though I have not tested it.

Overall, I just think it's a neat approach.

Intimist answered 28/1, 2019 at 0:44 Comment(0)
B
3

In GNUC++ (seems to be obsolete since 2.5, a long time ago :) See the answers here: C struct initialization using labels. It works, but how?), it is possible to initialize a struct like this:

struct inventory_item {
    int bananas;
    int apples;
    int pineapples;
};

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
Begley answered 4/2, 2019 at 10:45 Comment(0)
C
3

You have

  1. The standard initialization list

    address temp_address {
        /* street_no */,
        /* street_name */,
        ...
        /* postal_code */
    };
    
    address temp_address2 = {
        /* street_no */,
        /* street_name */,
        ...
        /* postal_code */
    }
    
  2. The dot notation

    address temp_address;
    temp_address.street_no = ...;
    temp_address.street_name = ...;
    ...
    temp_address.postal_code = ...;
    
  3. The designated aggregate initialization, where the initialization list contains that labels of each member of the structure (see documentation) available from C++20 onward.

  4. Treating a struct like a C++ class - in C++ structures are actually special types of classes, where all members are public (unlike a standard C++ class where all members are private if not specified otherwise explicitly) as well as that when using inheritance they default to public:

    struct Address {
        int street_no;
        ...
        char* postal_code;
    
        Address (int _street_no, ... , char* _postal_code)
         : street_no(_street_no),
           ...
           postal_code(_postal_code)
        {}
    }
    
    ...
    
     Address temp_address ( /* street_no */, ..., /* postal_code */);
    

When it comes to the way you initialize your structure you should consider the following aspects:

  • Portability - different compilers, different degree of C++ standard completeness and different C++ standards altogether do limit your options. If you have to work with let's say a C++11 compiler but want to use the C++20 designated aggregate initialization you are out of luck
  • Readability - what is more readable: temp_address.city = "Toronto" or temp_address { ..., "Toronto", ... }? Readability of your code is very important. Especially when you have large structures (worse - nested ones), having unlabeled values all over the place is just asking for trouble
  • Scalability - anything that depends on a specific order is not a good idea. The same goes for lack of labels. You want to move a member up or down the address space of the structure? Good luck with an unlabeled initialization list (hunting down swapped values in structure initialization is a nightmare)... You want to add a new member? Again good luck with anything that depends on a specific order.

While the dot notation means you type more the benefits you get from using it outweigh this issue and as such I can recommend it unless you have a small structure that is future-proof in terms of lack of changes in its structure, in which case you can afford to go with an initialization list. Remember: whenever working with other people writing code that is easy to follow is essential.

Cartilage answered 16/12, 2021 at 15:44 Comment(0)
R
1

I faced a similar problem today, where I have a struct that I want to fill with test data which will be passed as arguments to a function I'm testing. I wanted to have a vector of these structs and was looking for a one-liner method to initialize each struct.

I ended up going with a constructor function in the struct, which I believe was also suggested in a few answers to your question.

It's probably bad practice to have the arguments to the constructor have the same names as the public member variables, requiring use of the this pointer. Someone can suggest an edit if there is a better way.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Which I used in in my test function to call the function being tested with various arguments like this:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}
Retrospective answered 3/4, 2015 at 22:45 Comment(0)
T
1

It is possible, but only if the struct you're initializing is a POD (plain old data) struct. It cannot contain any methods, constructors, or even default values.

Trochal answered 11/8, 2015 at 19:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.