C++ Equivalent to Designated Initializers?

18,045

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++.

Share:
18,045
James Snyder
Author by

James Snyder

Updated on June 25, 2022

Comments

  • James Snyder
    James Snyder about 2 years

    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).

  • James Snyder
    James Snyder about 15 years
    I have considered that, and may end up ultimately going that route. Unfortunately there's one platform that we'd like to port to that uses a C++ library to provide perhipheral support. They provide access to a compiler online, but they've limited it to a C++-only mode (for simplicity). I've tried grabbing the precompiled library and using a local GCC toolchain, but there are some symbols that aren't resolved from their library (They use Keil/ARM's RealView) when linking with GCC & Newlib. I could pre-compile all the C99 code locally and link online. I'm just trying to keep things simple :-)
  • Adam Rosenfield
    Adam Rosenfield about 15 years
    That's only because 42L is implicitly cast to an integer. If you wanted to initialize the float member to, say, 3.5, you can't do that in C++.
  • Nathaniel Sharp
    Nathaniel Sharp about 15 years
    You can not initialize more than one member of a union. It's a union after all ;-) However if you want to initialize the "float" part you will need to initialize it with the integer equivalent (probably as a hex number)
  • James Snyder
    James Snyder about 15 years
    Thanks. It doesn't necessarily have to compile as C99. The initializers are in a number of places, but they're defined using some macros, so something where I can ifdef __cplusplus with a minimal modification, that's fine as well.
  • Adam Rosenfield
    Adam Rosenfield about 15 years
    Yes -- but C99's designated initializers let you initialize an element of a union other than the first without resorting to hacks such as figuring out a float's implementation-defined integer equivalent.
  • James Snyder
    James Snyder about 15 years
    I'm not asking to initialize more than one member, just not the first one all the time. The reason it's a bit more complicated than doing things with equivalents is that some of them are more complex members of the union. I could point to our sources (modified Lua to run on small devices), but it takes a little digging to understand how things work. If there's interest, here's a starting point: svn.berlios.de/svnroot/repos/elua/trunk/src/lua/lrotable.h Description (yeah, it shows up raw, docs aren't elsewhere yet): svn.berlios.de/svnroot/repos/elua/trunk/doc/en/arch_ltr.html
  • Nathaniel Sharp
    Nathaniel Sharp about 15 years
    Well if you want to compile it as standard C++ I am afraid you need to use hacks like the "equivalents". And you will only need them for the data that goes into ROM, for all other data that goes into RAM you can create regular constructors like others have pointed out.
  • James Snyder
    James Snyder about 15 years
    Is the initialisation done at runtime or compile time? That's, I suppose, the critical issue here.
  • Shing Yip
    Shing Yip about 15 years
    I'll have to consult the holly standard to be sure, but just off my head, i think all global static variables are initialized and stored in the data section of executable image. Your best bet is to try it out with your compiler and see what it does.
  • Sam
    Sam over 12 years
    Works for me - not in a fast-path section anyway. Better solution than using another C file! Upvote!
  • Asaf
    Asaf over 12 years
    Again with being late, but this just doesn't work if you swap jmcd.x and jmcd.y in the first solution. This is because it's not a special construct, it's just regular initialization with more expressions. So jmcd.x = 12 is executed, and then the result value for this expression (12) is assigned to the first field of the struct (x). The same for y. If you swap them, both fields will be 12.
  • Tom
    Tom over 12 years
    No, the space will be allocated in the executable image, initialised to zeros and the constructor called at runtime. Just to add to the fun, though, embedded systems can be a bit inconsistent on this last point - in theory the linker collects a list of static constructor calls and then libgcc calls them during process bootstrapping, but depending on your platform it can just not happen, or only happen if you select the right build options.
  • chys
    chys over 11 years
    Support for designated initializers in C++ is a GNU extension, and not part of C++11. Even with the GCC extension, designated initialization is only allowed for POD types.
  • Zimano
    Zimano over 8 years
    This could work, but I believe this will get messy pretty fast if you use non-standard types for members of your unions/structs.