Search code examples
c++structinitializationstdinitializer-list

How to initialize structures using fields names?


I want to initialize many different objects of some type (structure) with different values for their fields and I want to use names for initialization to make code readable and robust.

Initializer lists for structures have two killing disadvantages:

  1. They are nameless; if I have a dozen of parameters of the same type (colors of UI elements), it is easy to get mess of them; hard to see what each of them means and comments don’t help and could even mislead because of (2).
  2. The order of structure fields is an established contract (or API, or interface); if tomorrow a developer will change the order of fields (better memory fit, more frequent usage, alphabetical order, etc.), it will ruin all initializers lists with the values of the same type at unpredictable far places of the code which are quite hard to find even if you realized what has happened.

Here is the task I am trying to solve. I have configuration for application which in this particular case I don’t want to put in external file and want to have compiled-in. This is very close to How to define C++ struct for configuration?, but I am not ready to put frequently changing parameters to a header file and cause whole application recompilation with every change of the value in the params source code (specifics or R&D project, many things to tune frequently). So, I put actual values in cpp file.

Let’s consider the code (please don’t stuck on the specific example, the UI colors here just for simplicity of the example): params.h

struct RGB { unsigned char r=0, g=0, b=0; };

struct UIColors {
    RGB background;
    RGB font;
};

namespace params {
    extern bool ui_bright_mode;

    extern UIColors night_mode;
    extern UIColors bright_mode;
}

params.cpp

namespace params {
    bool ui_bright_mode = 1;

    UIColors night_mode  = { 
        /*background*/ {1, 1, 1}, 
        /*font*/ {0, 0, 200} 
    };

    UIColors bright_mode = { 
        /*background*/ { 255, 255, 255 }, 
        /*font*/ { 50, 50, 50 } 
    };
}

application.cpp

#include <iostream>

UIColors& ui_colors() {
    return params::ui_bright_mode ? params::bright_mode : params::night_mode;
}

int main()
{
    std::cout << params::ui_bright_mode << '\n';

    std::cout 
        << (int) ui_colors().background.r << ", "
        << (int) ui_colors().background.g << ", "
        << (int) ui_colors().background.b << '\n';

    params::ui_bright_mode = 0;

    std::cout << params::ui_bright_mode << '\n';

    std::cout
        << (int)ui_colors().background.r << ", "
        << (int)ui_colors().background.g << ", "
        << (int)ui_colors().background.b << '\n';
}

Full Demo (in one file, since it is hard to make many there).

The question is, is there any reasonable and robust approach which could allow me to keep the values in C++ file (params.cpp above) and at the same time make initialization in a way I have for ui_bright_mode variable, by fields’ names?

I would be happy to have something like:

UIColors night_mode  = { 
    background = {1, 1, 1}, 
    font = {0, 0, 200}
};

at the same time

RGB background = {1, 1, 1};
RGB font = {0, 0, 200};
UIColors night_mode  = { 
    background, 
    font
};

would be the case, but two verbose and still won't defend from fields order changes and mess or similar fields; still hard to find a proper place for this "temporary variables" and still not always applicable.

I can’t use nested namespaces because I want to have a luxury to copy structures or select one of them as in the example above with the ui_colors() function. This would be impossible with the namespaces.

I would be happy to avoid heavy macro-based solution, preferring something language-based or std-based. And, of course, I want to avoid using maps to store values because of performance and simplicity considerations.


Solution

  • Designated Initializers

    The best way I know to initialize structures using field names is using designated initializers like in Gene's comment. It's available in C++20, and fitting to your specifications of keeping initialization readable and minimally verbose. Leveraging on designated initializers allows you to explicitly name fields during initialization.

    Here is an example muching to yours:

    //params.cpp
    namespace params 
    {
        bool ui_bright_mode = true;
    
        UIColors night_mode = 
        {
            .background = {1, 1, 1},
            .font = {0, 0, 200}
        };
    
        UIColors bright_mode =
        {
            .background = {255, 255, 255},
            .font = {50, 50, 50}
        };
    }
    

    Here is a full demo . Each field is explicitly named during initialization with the benefit of no overhead. Moreover, this method is order independent, so changes to field order in UIColors won't break the code. It's Supported in GCC since version 10 and in Clang since version 9. And again I'll say, make sure you're using c++20. Good luck with your project!