C++ Equivalent to Designated Initializers?
Recently I've been working on some embedded devices, where we have some structs and unions that need to be initialized at compile time so that we can keep certain things in flash or ROM that don't need to be modified, and save a little flash or SRAM at a bit of a performance cost. Currently the code compiles as valid C99, but without this adjustment it used to compile as C++ code as well, and it would be great to support things being compiled that way as well. One of the key things that prevents this is that we're using C99 designated initializers which do not work within the C subset of C++. I'm not much of a C++ buff, so I'm wondering what simple ways there might be to make this happen in either C++ compatible C, or in C++ that still allow initialization at compile time so that the structs and unions not need be initialized after program startup in SRAM.
One additional point of note: a key reason for designated initializer usage is initalizing as NOT the first member of a union. Also, sticking with standard C++ or ANSI C is a plus in order to maintain compatibility with other compilers (I know about the GNU extensions that provide something like designated initializers without C99).
Solution 1:
I'm not sure you can do it in C++. For the stuff that you need to initialize using designated initializers, you can put those separately in a .c
file compiled as C99, e.g.:
// In common header file
typedef union my_union
{
int i;
float f;
} my_union;
extern const my_union g_var;
// In file compiled as C99
const my_union g_var = { .f = 3.14159f };
// Now any file that #include's the header can access g_var, and it will be
// properly initialized at load time
Solution 2:
Building on Shing Yip's answer, and with benefit of 3 year's time, C++11 can now guarantee compile time initialization:
union Bar
{
constexpr Bar(int a) : a_(a) {}
constexpr Bar(float b) : b_(b) {}
int a_;
float b_;
};
extern constexpr Bar bar1(1);
extern constexpr Bar bar2(1.234f);
Assembly:
.globl _bar1 ## @bar1
.p2align 2
_bar1:
.long 1 ## 0x1
.globl _bar2 ## @bar2
.p2align 2
_bar2:
.long 1067316150 ## float 1.23399997
Solution 3:
#ifdef __cplusplus
struct Foo
{
Foo(int a, int b) : a(a), b(b) {}
int a;
int b;
};
union Bar
{
Bar(int a) : a(a) {}
Bar(float b) : b(b) {}
int a;
float b;
};
static Foo foo(1,2);
static Bar bar1(1);
static Bar bar2(1.234f);
#else
/* C99 stuff */
#endif // __cplusplus
In C++ union can have constructors too. May be this is what you wanted?
Solution 4:
This is sort of both an answer and a question. I realize this thread is dead, but it exactly what I was looking into tonight.
I did some poking around and the closest thing I can get to what I want (which is similar to what you want... I have been working with pics and have no need to use c++, but I am curious how it might be done) is the first code example:
#include <iostream>
using namespace std;
extern "C"
{
typedef struct stuff
{
int x;
double y;
} things;
}
int main()
{
things jmcd = { jmcd.x = 12, jmcd.y = 10.1234 };
cout << jmcd.x << " " << jmcd.y << endl;
return 0;
}
This has a very similar appearance to the C99 style designated initializers with a caveat I will mention later. (You would probably wrap this in #ifdef __cplusplus if you wanted the struct to be compiled by either.) The second version of code I looked at is this:
#include <iostream>
using namespace std;
extern "C"
{
typedef struct stuff
{
int x;
double y;
} things;
}
int main()
{
things jmcd;
jmcd.x = 12;
jmcd.y = 10.1234;
cout << jmcd.x << " " << jmcd.y << endl;
return 0;
}
Basically, from looking at the disassembly, it appears the first example is actually slower. I have looked at the assembly output and, well, I must be a little rusty. Maybe someone could give me some insight. The assembly output of the first cpp compiled and looked like:
main:
.LFB957:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $24, %esp
movl $0, 12(%esp)
movl $0, 16(%esp)
movl $0, 20(%esp)
movl $12, 12(%esp)
movl 12(%esp), %eax
movl %eax, 12(%esp)
fldl .LC0
fstpl 16(%esp)
fldl 16(%esp)
fstpl 16(%esp)
movl 12(%esp), %eax
movl %eax, 4(%esp)
fildl 4(%esp)
fldl 16(%esp)
faddp %st, %st(1)
fnstcw 2(%esp)
movzwl 2(%esp), %eax
movb $12, %ah
movw %ax, (%esp)
fldcw (%esp)
fistpl 4(%esp)
fldcw 2(%esp)
movl 4(%esp), %eax
leave
ret
.cfi_endproc
The second example looked like:
main:
.LFB957:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $24, %esp
movl $12, 12(%esp)
fldl .LC0
fstpl 16(%esp)
movl 12(%esp), %eax
movl %eax, 4(%esp)
fildl 4(%esp)
fldl 16(%esp)
faddp %st, %st(1)
fnstcw 2(%esp)
movzwl 2(%esp), %eax
movb $12, %ah
movw %ax, (%esp)
fldcw (%esp)
fistpl 4(%esp)
fldcw 2(%esp)
movl 4(%esp), %eax
leave
ret
.cfi_endproc
Both of these were generated with a g++ -O0 -S main.cpp
command. Clearly, the intuitively less efficient example generated more efficient opcode in terms of number of instructions. On the other hand, there are few cases where I could imagine the few instructions being critical. (On the other hand, I really have trouble understanding assembly not written by humans, so maybe I am missing something... ) I think this provides a solution, albeit late, to the question James asked. The next thing I should test is if the same initialization is allowed in C99; if that works, I think it fully addresses James's problem.
Disclaimer: I have no idea if this works or behaves similarly for any other compilers other than g++.