28

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?

101010
  • 41,839
  • 11
  • 94
  • 168
  • Not just the default constructor. – chris Apr 21 '14 at 22:50
  • 9
    How do you envision the initialization taking place "at compile time"? What does that mean to you? – Cheers and hth. - Alf Apr 21 '14 at 22:53
  • If you had a global instance of `struct foo`, then it is likely that it will be initialized "at compile time" during static initialization. Most likely the compiler would allocate the variable in the .data section and initialize it with the values. Thus the global instance would be initialized when the executable is loaded. – Graznarak Apr 22 '14 at 01:59

5 Answers5

28

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.

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • 1
    Very nice explanation. The experimental approach is really fascinating and tutoring. One question though, in the comments @Graznarak mentioned (**"If you had a global instance of struct foo, then it is likely that it will be initialized "at compile time" during static initialization"**) is this possible? – 101010 Apr 23 '14 at 20:43
  • Just be aware that an experiment only allows us to probe the behavior of one specific implementation in the exact circumstances tested. Better program by contract than by happenstance. – Deduplicator May 02 '14 at 11:33
  • @40two Regardless of whether the object is global, or `const`, it's not a compile-time value. Even if its initial value is computed at compile time, that's an optimization. But what Nikos should have mentioned is that you can create a compile-time object instance by using `constexpr` - his example works if `a` is declared as such. It's a bit misleading to say that using a `const static` member is the answer here. – scry May 04 '14 at 21:42
  • The obvious follow up question is, are these member variables initialised before or after the constructor? I think that's what OP was asking, and I don't think anyone's answered it. – Owl May 24 '21 at 15:51
  • @Owl Before the constructor there is no instance. After the constructor the object is formed. The variables are initialized **in** the constructor (that's what constuctors do), specifically "in" the member initialization list, iff the constructor does not specify an alternative member initialization. Check this with an experiment (like the one in this answer) http://coliru.stacked-crooked.com/a/5bd1c1d125fcf993 or (as many people seem to prefer) check http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4659.pdf 15.6.2 (Initializing bases and members) clause 10. – Nikos Athanasiou May 24 '21 at 17:37
  • Indeed, but the confusion arises when static variables in C exist always in the code, before and after scope. So if you have a static member variable, before the class is instantiated, does that variable exist and is it assigned the value? That's what the question is, and that's my question too. – Owl May 24 '21 at 23:14
  • @Owl The question is about "in class" member variable initialization which was introduced in C++11. Static member variable initialization is a different concept, I only mention it to show an example of compile time computation. A static member variable's initialization is independent from a constructor, since they aren't part of an instance (you won't find them in `this->` pointer); instead static members exist w/o an object (you can look them up with `ClassType::`). It may sound complicated & it is, check this to get a feeling https://accu.org/journals/overload/25/139/brand_2379/ – Nikos Athanasiou May 25 '21 at 07:59
3

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.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
2

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.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Unless it doesn't happen at run-time. – Deduplicator Apr 21 '14 at 23:13
  • @Daniel Frey First of all thanks for the answer, could you please be so kind to elaborate on whether the same is valid for static members as well, or these members are initialized during compile time? – 101010 Apr 21 '14 at 23:14
  • According to Bjarne Stroustup's FAQ (http://www.stroustrup.com/C++11FAQ.html#member-init) the initialization can be non-const so that it's in the general case not possible to perform it at compile time. (Think opening a database connection.) But @Deduplicator's comment holds that due to the "as-if" rule e.g. nulling of static PODs can certainly be done at compile time. – Peter - Reinstate Monica Apr 21 '14 at 23:33
1

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.

Matthew Fioravante
  • 1,478
  • 15
  • 19
0

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

M.M
  • 138,810
  • 21
  • 208
  • 365