170

I'm trying to find a convenient way to initialise 'pod' C++ structs. Now, consider the following struct:

struct FooBar {
  int foo;
  float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;

If I want to conveniently initialise this in C (!), I could simply write:

/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C

Note that I want to explicitly avoid the following notation, because it strikes me as being made to break my neck if I change anything in the struct in the future:

/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?

To achieve the same (or at least similar) in C++ as in the /* A */ example, I would have to implement an annoying constructor:

FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);

Which feels redundant and unnecessary. Also, it is pretty much as bad as the /* B */ example, as it does not explicitly state which value goes to which member.

So, my question is basically how I can achieve something similar to /* A */ or better in C++? Alternatively, I would be okay with an explanation why I should not want to do this (i.e. why my mental paradigm is bad).

EDIT

By convenient, I mean also maintainable and non-redundant.

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • 2
    I think the B example is as close as you are going to get. – Marlon May 31 '11 at 00:03
  • 2
    I don't see how example B is "bad style." It makes sense to me, since you're initializing each member in turn with their respective values. – Mike Bailey May 31 '11 at 03:04
  • 33
    Mike, it's bad style because it is not clear which value goes to which member. You have to go and look at the definition of the struct and then count members to find what each value means. – jnnnnn Jan 10 '13 at 00:56
  • 12
    Plus, if the definition of FooBar were to change in the future, the initialization could become broken. – Edward Falk Mar 12 '13 at 20:44
  • if initialization gets long and complex, don't forget about the builder pattern – sled Jan 06 '16 at 00:17
  • I've actually used the "A" style in my C++ project and it worked fine with GCC, then I tried to build it with MSVC and it didn't quite like it. So you can use it as long as you don't mind the non-cross-compiler-ness. – Grishka Jul 02 '17 at 05:12
  • To achieve what you want, call your local Congressman and ask him to vote for [Proposal P0329R3](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r3.pdf) during the next C++ Standards Committee meeting. – max Oct 12 '17 at 23:07

14 Answers14

48

Designated initializes will be supported in c++2a, but you don't have to wait, because they are officialy supported by GCC, Clang and MSVC.

#include <iostream>
#include <filesystem>

struct hello_world {
    const char* hello;
    const char* world;
};

int main () 
{
    hello_world hw = {
        .hello = "hello, ",
        .world = "world!"
    };
    
    std::cout << hw.hello << hw.world << std::endl;
    return 0;
}

GCC Demo MSVC Demo

Update 2021

As @Code Doggo noted, anyone who is using Visual Studio 2019 will need to set /std:c++latest  for the "C++ Language Standard" field contained under Configuration Properties -> C/C++ -> Language.

ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • 2
    Caveat emptor: keep in mind that if you add parameters to the end of the struct later, old initializations will still silently compile without having been initialized. – Catskul Jun 26 '19 at 18:18
  • 2
    @Catskul No. It [will be initialized](http://eel.is/c++draft/dcl.init.aggr#5.2) with empty initializer list, which will result into initialization with zero. – ivaigult Jun 27 '19 at 11:55
  • You're right. Thank you. I should clarify, the remaining parameters will silently be effectively default initialized. The point I meant to make was that anyone hoping this might help enforce complete explicit initialization of POD types will be disappointed. – Catskul Jun 27 '19 at 15:31
  • 3
    As of Dec 31st, 2020, anyone who is using Visual Studio 2019 will need to set `/std:c++latest ` for the "*C++ Language Standard*" field contained under `Configuration Properties -> C/C++ -> Language`. This will provide access to the C++20 features currently available under development. C++20 is not available as complete and finalized implementation for Visual Studio yet. – Code Doggo Dec 31 '20 at 21:29
  • 3
    20201 ? Golly gee I took a loooong nap! – CaptainCodeman Jun 25 '22 at 11:26
47

Since style A is not allowed in C++ and you don't want style B then how about using style BX:

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

At least help at some extent.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 9
    +1: it does not really ensure correct initialization (from the compiler POV) but sure helps the reader... although the comments ought to be kept in sync. – Matthieu M. May 31 '11 at 06:42
  • 25
    Comment doesn't prevent initialization of the structure from being broken if I insert new field between `foo` and `bar` in the future. C would still initialize the fields we want, but C++ would not. And this is the point of the question - how to achieve the same result in C++. I mean, Python does this with named arguments, C - with "named" fields, and C++ should have something too, I hope. – dmitry_romanov Jul 18 '13 at 06:00
  • 3
    Comments in sync? Give me a break. Safety goes through the window. Reorder the parameters and boom. Much better with `explicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) `. Note the **explicit** keyword. Even breaking the standard is better in regards to safety. In Clang: -Wno-c99-extensions – Daniel O Jan 20 '15 at 13:39
  • @DanielW, It's not about what is better or what is not. this answer in accordance that the OP doesn't want Style A (not c++), B or C, which covers all the valid cases. – iammilind Feb 01 '15 at 14:23
  • @iammilind I think a hint as to why OP's _mental paradigm is bad_ could improve the answer. I consider this dangerous as it is now. – Daerst Feb 10 '15 at 15:25
  • @iammilind why are we even considering the `=` here? There's no need of that! – Paiusco Dec 17 '20 at 17:19
  • @DanielO that is a pointless when you are trying to do static initialization and code can't run yet ffs! – Enerccio Sep 01 '22 at 12:00
13

You could use a lambda:

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

More information on this idiom can be found on Herb Sutter's blog.

eyelash
  • 3,197
  • 25
  • 33
  • 2
    Such approach initializes fields twice. Once in constructor. Second is `fb.XXX = YYY`. – Dmytro Ovdiienko Nov 14 '18 at 14:36
  • 1
    @DmytroOvdiienko No, copy elision will prevent that from occurring: https://en.cppreference.com/w/cpp/language/copy_elision – Moop Aug 30 '22 at 00:00
  • @Moop I meant that fields might be initialized at first in the default ctor and then in the `fb.XXX = YYY` expression. But as author stated that it is POD type, so that is not a case anymore. – Dmytro Ovdiienko Aug 30 '22 at 10:01
9

Extract the contants into functions that describe them (basic refactoring):

FooBar fb = { foo(), bar() };

I know that style is very close to the one you didn't want to use, but it enables easier replacement of the constant values and also explain them (thus not needing to edit comments), if they ever change that is.

Another thing you could do (since you are lazy) is to make the constructor inline, so you don't have to type as much (removing "Foobar::" and time spent switching between h and cpp file):

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};
rtn
  • 127,556
  • 20
  • 111
  • 121
  • 2
    I highly recommend anyone else reading this question to choose the style in the bottom code-snippet for this answer if all you're looking to do is be able to quickly initialize structs with a set of values. – kayleeFrye_onDeck Feb 17 '18 at 01:00
8

Your question is somewhat difficult because even the function:

static FooBar MakeFooBar(int foo, float bar);

may be called as:

FooBar fb = MakeFooBar(3.4, 5);

because of the promotion and conversions rules for built-in numeric types. (C has never been really strongly typed)

In C++, what you want is achievable, though with the help of templates and static assertions:

template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
  static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
  static_assert(std::is_same<Real, float>::value, "bar should be of type float");
  return { foo, bar };
}

In C, you may name the parameters, but you'll never get further.

On the other hand, if all you want is named parameters, then you write a lot of cumbersome code:

struct FooBarMaker {
  FooBarMaker(int f): _f(f) {}
  FooBar Bar(float b) const { return FooBar(_f, b); }
  int _f;
};

static FooBarMaker Foo(int f) { return FooBarMaker(f); }

// Usage
FooBar fb = Foo(5).Bar(3.4);

And you can pepper in type promotion protection if you like.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    "In C++, what you want is achievable": wasn't OP asking to help prevent the mixup of the order of parameters? How does the template you propose would achieve that? Just for simplicity, let's say we have 2 parameters, both of them int. – max Oct 12 '17 at 23:01
  • @max: It will prevent it only if the types differ (even if they are convertible to each other), which is the OP situation. If it cannot distinguish the types, then of course it doesn't work, but that's a whole other question. – Matthieu M. Oct 13 '17 at 15:44
  • Ah got it. Yeah, these are two different problems, and I guess the second one doesn't have a good solution in C++ at the moment (but it appears C++ 20 is adding the support for the C99-style parameter names in the aggregate initialization). – max Oct 13 '17 at 21:35
6

Many compilers' C++ frontends (including GCC and clang) understand C initializer syntax. If you can, simply use that method.

Matthias Urlichs
  • 2,301
  • 19
  • 29
4

Yet another way in C++ is

struct Point
{
private:

 int x;
 int y;

public:
    Point& setX(int xIn) { x = Xin; return *this;}
    Point& setY(int yIn) { y = Yin; return *this;}

}

Point pt;
pt.setX(20).setY(20);
parapura rajkumar
  • 24,045
  • 1
  • 55
  • 85
  • 2
    Cumbersome for functional programming (i.e. creating the object in the argument list of a function call), but really a neat idea otherwise! – bitmask May 31 '11 at 00:22
  • 1
    I seriously doubt any good optimizer doesn't reduce it both to equivalent statements – parapura rajkumar May 31 '11 at 00:28
  • 30
    the optimizer probably reduces it, but my eyes don't. – Matthieu M. May 31 '11 at 06:41
  • As an example it is not that good, as most people will realize that `Point pt(20, 20);` will set the coordinates of the point. If the first parameter is `y` and the second is `x` that's a design error and not a problem with the initialization syntax. :-) – Bo Persson May 31 '11 at 07:17
  • 6
    Two words: argh...argh! How is this better than using public data with 'Point pt; pt.x = pt.y = 20;`? Or if you want encapsulation, how is this better than a constructor? – OldPeculier Jun 14 '12 at 17:44
  • 3
    It is better than a constructor because you have to look at the constructor declaration for the parameter order ... is it x , y or y , x but the way I have showed it is evident at call site – parapura rajkumar Jun 14 '12 at 18:42
  • 2
    This does not work if you want a const struct. or if you want to tell the compiler not to allow uninitialized structs. If you *really* want to do it this way, at least mark the setters with `inline`! – Matthias Urlichs Oct 07 '15 at 02:55
3

I know this question is old, but there is a way to solve this until C++20 finally brings this feature from C to C++. What you can do to solve this is use preprocessor macros with static_asserts to check your initialization is valid. (I know macros are generally bad, but here I don't see another way.) See example code below:

#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."

#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\

// Create more macros for structs with more attributes...

Then when you have a struct with const attributes, you can do this:

struct MyStruct
{
    const int attr1;
    const float attr2;
    const double attr3;
};

const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);

It's a bit inconvenient, because you need macros for every possible number of attributes and you need to repeat the type and name of your instance in the macro call. Also you cannot use the macro in a return statement, because the asserts come after the initialization.

But it does solve your problem: When you change the struct, the call will fail at compile-time.

If you use C++17, you can even make these macros more strict by forcing the same types, e.g.:

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\
Max Vollmer
  • 8,412
  • 9
  • 28
  • 43
3

Option D:

FooBar FooBarMake(int foo, float bar)

Legal C, legal C++. Easily optimizable for PODs. Of course there are no named arguments, but this is like all C++. If you want named arguments, Objective C should be better choice.

Option E:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

Legal C, legal C++. Named arguments.

John
  • 2,295
  • 1
  • 20
  • 25
2

The way /* B */ is fine in C++ also the C++0x is going to extend the syntax so it is useful for C++ containers too. I do not understand why you call it bad style?

If you want to indicate parameters with names then you can use boost parameter library, but it may confuse someone unfamiliar with it.

Reordering struct members is like reordering function parameters, such refactoring may cause problems if you don't do it very carefully.

Öö Tiib
  • 10,809
  • 25
  • 44
  • 7
    I call it bad style because I think it is zero maintainable. What if I add another member in a year? Or if I change the ordering/types of the members? Every piece of code initialising it might (very likely) break. – bitmask May 31 '11 at 00:12
  • 2
    @bitmask But as long as you do not have named arguments, you would have to update constructor calls, too and I think not many people think constructors are unmaintainable bad style. I also think named initialization is not C, but C99, of which C++ is definitely not a superset. – Christian Rau May 31 '11 at 00:18
  • 2
    If you add another member in a year to end of the struct then it will be default-initialized in already existing code. If you reorder them then you have to edit all existing code, nothing to do. – Öö Tiib May 31 '11 at 00:19
  • 1
    @bitmask: The first example would be "unmaintainable" as well then. What happens if you rename a variable in the struct instead? Sure, you could do a replace-all, but that could accidentally rename a variable that shouldn't be renamed. – Mike Bailey May 31 '11 at 03:06
  • @ChristianRau Since when is C99 not C? Isn't C the group and C99 a particular version/ISO specification? – altendky Jan 17 '14 at 19:08
  • @altendky Yeah, of course C99 is C. But this isn't relevant to the question at all, since this particular C99-feature (named initialization) is *not* part of the C subset included in C++, not even of the C99 parts included in C++11. But that comment might have been worded not that accurately, I agree. – Christian Rau Jan 17 '14 at 19:26
  • @ÖöTiib `If you add another member in a year to end of the struct then it will be default-initialized in already existing code.` - which is even worse, because now I have to go through every initialisation and count number of arguments to make sure that I did not forgot to add the new value. – Dmitry Zaytsev Jun 22 '16 at 08:55
  • @DmitryZaitsev Worse than what? If maintenance modifies layout of data then they have to go through every initialization. If for nothing else then just to verify that the `/* A */` was always used since `/* B */` is also legal C. – Öö Tiib Jun 22 '16 at 09:23
1

What about this syntax?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Just tested on a Microsoft Visual C++ 2015 and on g++ 6.0.2. Working OK.
You can make a specific macro also if you want to avoid duplicating variable name.

cls
  • 501
  • 1
  • 5
  • 18
  • `clang++` 3.5.0-10 with `-Weverything -std=c++1z` seems to confirm that. But it doesn't look right. Do you know where the standard confirms that this is valid C++? – bitmask Nov 30 '16 at 16:34
  • I do not know, but I've used that in different compilers since long time ago and did not see any problems. Now tested on g++ 4.4.7 - works fine. – cls Nov 30 '16 at 17:57
  • 6
    I don't think this work. Try `ABCD abc = { abc.b = 7, abc.a = 5 };`. – raymai97 Sep 24 '17 at 16:56
  • @deselect, it works because field is initialized with value, returned by operator=. So, actually you initialize class member twice. – Dmytro Ovdiienko Nov 14 '18 at 15:14
1

For me the laziest way to allow inline inizialization is use this macro.

#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;

struct foo {
    METHOD_MEMBER(string, attr1, foo)
    METHOD_MEMBER(int, attr2, foo)
    METHOD_MEMBER(double, attr3, foo)
};

// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);

That macro create attribute and self reference method.

Emanuele Pavanello
  • 785
  • 1
  • 9
  • 22
  • this approach ins't applicable if you can't change the struct's declaration (when it, for instance, resides in a third-party library) – AntonK Nov 09 '22 at 10:39
0

For versions of C++ prior to C++20 (which introduces the named initialization, making your option A valid in C++), consider the following:

int main()
{
    struct TFoo { int val; };
    struct TBar { float val; };

    struct FooBar {
        TFoo foo;
        TBar bar;
    };

    FooBar mystruct = { TFoo{12}, TBar{3.4} };

    std::cout << "foo = " << mystruct.foo.val << " bar = " << mystruct.bar.val << std::endl;

}

Note that if you try to initialize the struct with FooBar mystruct = { TFoo{12}, TFoo{3.4} }; you will get a compilation error.

The downside is that you have to create one additional struct for each variable inside your main struct, and also you have to use the inner value with mystruct.foo.val. But on the other hand, it`s clean, simple, pure and standard.

Nelson
  • 54
  • 4
-1

I personally have found that using constructor with struct is the most pragmatic way to ensure struct members are initialized in code to sensible values.

As you say above, small downside is that one does not immediatelly see what param is which member, but most IDEs help here, if one hovers over the code.

What I consider more likely is that new member is added and in this case i want all constructions of the struct to fail to compile, so developer is forced to review. In our fairly large code base, this has proven itself, because it guides developer in what needs attention and therefore creates self-maintained code.

  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31001087) – LordWilmore Feb 09 '22 at 16:27