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?
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 comparex_1
andx_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:
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 it is an option for you to make your constructor a const expression it will look this way:
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).