Home » C++ » Does in class member initialization takes place at compile time or run-time?

Does in class member initialization takes place at compile time or run-time?

Posted by: admin January 9, 2018 Leave a comment

Questions:

In C++11 a new feature was introduced where the programmer can initialize class member variables inside class’s definition, see code below:

struct foo
{ 
  int size = 3;
  int id   = 1;
  int type = 2;
  unsigned char data[3] = {'1', '2', '3'};
};

Is this initialization takes place during compile time or this feature is just syntactic sugar and member variables are initialized in the default constructor?

Answers:

First of all yes, as stated before, it is syntactic sugar. But since the rules can be too much to remember, here’s a logical experiment to help you figure out what happens in compile time and what not

You have your c++11 class that features in class initializers

struct foo { int size = 3; };

And another class that will help us with our experiment

template<int N>
struct experiment { enum { val = N }; };

Let our hypothesis H0 be that initialization does happen in compile time, then we could write

foo                a;
experiment<a.size> b;

No luck, we fail to compile. One could argue that failure is due to foo::size being non constant so lets try with

struct foo { const int size = 3; }; // constexpr instead of const would fail as well

Again, as gcc informs us

the value of ‘a’ is not usable in a constant expression

experiment b;

or (more clearly) visual studio 2013 tells us

error C2975: ‘N’ : invalid template argument for ‘example’, expected compile-time constant expression

So, we have to discard H0 and deduce that initialization does not happen in compile time.

What would it take to happen in compile time

There is an old syntax that does the trick

struct foo { static const int size = 3; };

Now this compiles but beware this is (technically and logically) no longer in class initialization.

I had to lie for a little to make a point, but to expose the whole truth now : Message errors imply that a is the real problem. You see, since you have an instance for an object (Daniel Frey also mentions this) memory (for members) has to be initialized (at runtime). If the member was (const) static, as in the final example, then it’s not part of the subobjects of a(ny) class and you can have your initialization at compile time.

Questions:
Answers:

In-class initialisers for member-variables are syntactic sugar for writing them in the constructor initialiser list, unless there’s an explicit initialiser already there, in which case they are ignored.
In-class initialisers of static const members are for constant literals, a definition is still needed (though without initialiser).

C++ has the “as if”-rule from C, so anything resulting in the prescribed observed behavior is allowed.
Specifically, that means static objects may be initialised at compile-time.

Questions:
Answers:

It’s just syntactic sugar. Also consider that an instance usually means memory which has to be initialized with the correct values. Just because these values are provided with a different syntax does not change the fact that the memory needs to be initialized – which happens at run-time.

Questions:
Answers:

Its essentially syntactic sugar for a user provided constructor which initializes the values. You are providing default values for data members. When you ask whether this happens at compile time or run time, the answer depends on the context its used in.

Hopefully, these examples will help. Try them in http://gcc.godbolt.org and see the dissassembly and compilation errors.

struct S { int size = 3; };

//s's data members are compile time constants
constexpr S s = {};

//r's data members are run time constants
const S r = {};

//rr's data members are run time constants, 
//but we don't know the values in this translation unit
extern const S rr;

template <int X> class Foo {};

//Ok, s.size is a compile time expression
Foo<s.size> f; 

//Error, r.size is not a compile time expression
Foo<r.size> g; 

//Compile time expression, this is same as return 3;
int foo() { return s.size; }

//This also works
constexpr int cfoo() { return s.size; }

//Compiler will optimize this to return 3; because r.size is const.
int bar() { return r.size; }

//Compiler cannot optimize, because we don't know the value of rr.size
//This will have to read the value of rr.size from memory.
int baz() { return rr.size; }

As others have shown, static data members (and global variables, same thing essentially) for primitive types such as ints and floats have some weird rules where they can be const but still be used in compile time contexts as if they were constexpr. This is for backwards compatibility with C and the lack of the constexpr feature in the past. Its unfortunate now because it just makes understanding constexpr and what differentiates run time expressions from compile time expressions more confusing.

Questions:
Answers:

Having int size = 3; is exactly equivalent to having int size; and then each constructor that doesn’t already have size in its initializer list (including compiler-generated constructors) having size(3) there.

Strictly speaking C++ doesn’t have a distinction between “compile-time” and “run-time”.