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.
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.
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;
}
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
.
/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.
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.
fb.XXX = YYY
.
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;
};
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.
Many compilers' C++ frontends (including GCC and clang) understand C initializer syntax. If you can, simply use that method.
private: FooBar(float x, int y) {};
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);
inline
!
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.
FooBar fb = {};
in C++, it default-initializes all struct members.
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);\
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.
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.
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++?
ABCD abc = { abc.b = 7, abc.a = 5 };
.
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.
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.
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.
Success story sharing
foo
andbar
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.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