How to define a type so it can be static initialized?

134 Views Asked by At

I have been learning about trivial and standard layout types. I think I understand the basics behind it, but there is still something I am missing. Please have a look at the two following examples:

Example 1:

int main()
{
    struct SomeType
    {
        SomeType() = default;
        int x;
    };


    std::cout << "is_trivially_copyable: " << std::is_trivially_copyable<SomeType>::value << "\n";
    std::cout << "is_trivial: " << std::is_trivial<SomeType>::value << "\n";
    std::cout << "is_standard_layout: " << std::is_standard_layout<SomeType>::value << "\n";

    static constexpr SomeType someType{};
    
    return 0;
}

SomeType is trivial and I am able to static initialized it "static constexpr SomeType someType{};".

Example 2:

int main()
{
    struct SomeType
    {
        SomeType() {};
        int x;
    };


    std::cout << "is_trivially_copyable: " << std::is_trivially_copyable<SomeType>::value << "\n";
    std::cout << "is_trivial: " << std::is_trivial<SomeType>::value << "\n";
    std::cout << "is_standard_layout: " << std::is_standard_layout<SomeType>::value << "\n";

    static constexpr SomeType someType{};
    
    return 0;
}

SomeType is not trivial, but is standard layout, line "static constexpr SomeType someType{};" fails with an error "Error C2127 'someType': illegal initialization of 'constexpr' entity with a non-constant expression ConsoleApplication2" on MSVC compiler. If I make constructor constexpr and initialize x in constructor, this will work so my question is the following.

If I understood it right, trivial types can be static initialized, but what about non-trivial types? Let me maybe rephrase it for easier understanding, how to define a type so it can be statically initialized?

1

There are 1 best solutions below

2
On

The answer to your question is indeed not so clear. For example the programming language Ada has a whole mechanism called "elaboration" that specifies how constant data is defined and how constant data gets a value before code is executed. It must be precisely defined, what data is calculated at compile time and what data is compiled at runtime.

Coming to C++ it is also important to have this always in mind. Consider for example you have a constant constexpr float x_1 = a + b. That constant is calculated by the compiler using the addition function on the processor of the machine where you do the compilation.

Now consider a variable float x_2 = a + b. That is calculated during runtime. If you execute the code on the same machine, where you compile, the result may be the same. But if you execute it on another machine or even on another processor, the result may be even different (different rounding errors). When you compare x_1 and x_2 they may differ in an unexpected way. This example shall only illustrate how important it is to clearly know if things are calculated on the target machine (where the code runs) or on the host machine (where the code is compiled). Floating point operations are normally not identical (see How to keep float/double arithmetic deterministic?).

There can be also other situation where it is also of importance to exactly know what is calculated by the compiler and what is calculated at runtime (e.g. for the certification of safety critical software).

That been said, the answer to your question: It is clearly defined, what can be a const expression and what not. Allowed in const expressions is a subset of C++ that can be executed by the compiler alone in a kind of pre-compilation phase. The precise subset of C++ is defined as you can see here: https://en.cppreference.com/w/cpp/language/constant_expression

It should be clear that here no exhaustive explication for all these requirements of const expressions can be given. Luckily most things are intuitive. In details there are 37 languague constructs that are not allowed in const expressions.

In your case, the code does not compile because you have this construct:

  1. a function call expression that calls a function (or a constructor) that is not declared constexpr

That means, const expressions can only contain other const expressions calls but not arbitrary function calls. That make sense because your constructor could hypothetically have side effects and change some global variables. In that case it would not only produce something constant. The C++ language rules prevent you from doing such things.

The default constructor (which you have in your second example) is not a const expression. For more information about the default constructor that is created for you see here: https://en.cppreference.com/w/cpp/language/default_constructor

If this satisfies the requirements of a constexpr constructor (until C++23)constexpr function (since C++23), the generated constructor is constexpr.

If it is an option for you to make your constructor a const expression it will look this way:

    struct SomeType
    {
        constexpr SomeType() : x(0) {};
        int x;
    };

This is what you have already mentioned in your question. For me this compiles when I use the C++ standard C++20 with gcc. With this code you can see that the constructor is not trivial. So there is no relation between trivial types and the possibility of using it in static expressions (at least not with the gcc).