19

I have templated gray_code class which is meant to store some unsigned integer whose underlying bits are stored in Gray code order. Here it is:

template<typename UnsignedInt>
struct gray_code
{
    static_assert(std::is_unsigned<UnsignedInt>::value,
                  "gray code only supports built-in unsigned integers");

    // Variable containing the gray code
    UnsignedInt value;

    // Default constructor
    constexpr gray_code()
        = default;

    // Construction from UnsignedInt
    constexpr explicit gray_code(UnsignedInt value):
        value( (value >> 1) ^ value )
    {}

    // Other methods...
};

In some generic algorithm, I wrote something like this:

template<typename UnsignedInt>
void foo( /* ... */ )
{
    gray_code<UnsignedInt> bar{};
    // Other stuff...
}

In this piece of code, I expected bar to be zero-intialized and therefore bar.value to be zero-initialized. However, after having struggled with unexpected bugs, it appears that bar.value is initialized with garbage (4606858 to be exact) instead of 0u. That surprised me, so I went to cppreference.com to see what the line above was exactly supposed to do...


From what I can read, the form T object{}; corresponds to value initialization. I found this quote interesting:

In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.

However, gray_code has a user-provided constructor. Therefore it is not an aggregate thus aggregate initialization is not performed. gray_code has no constructor taking an std::initializer_list so list initialization is not performed either. The value-initialized of gray_code should then follow the usual C++14 rules of value initialization:

1) If T is a class type with no default constructor or with a user-provided default constructor or with a deleted default constructor, the object is default-initialized.

2) If T is a class type without a user-provided or deleted default constructor (that is, it may be a class with a defaulted default constructor or with an implicitly-defined one) then the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor.

3) If T is an array type, each element of the array is value-initialized.

4) Otherwise, the object is zero-initialized.

If I read correctly, gray_code has an explicitly defaulted (not user-provided) default constructor, therefore 1) does not apply. It has a defaulted default constructor, so 2) applies: gray_code is zero-initialized. The defaulted default constructor seems to meet all the requirements of a trivial default constructor, so default initialization should not happen. Let's have a look then at how gray_code is zero-initialized:

  • If T is a scalar type, the object's initial value is the integral constant zero implicitly converted to T.

  • If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits. The constructors, if any, are ignored.

  • If T is a union type, the first non-static named data member is zero-initialized and all padding is initialized to zero bits.

  • If T is array type, each element is zero-initialized

  • If T is reference type, nothing is done.

gray_code is a non-union class type. Therefore, all of its non-static data members should be initialized which means that value is zero-initialized. value satisfies std::is_unsigned and is therefore a scalar type, which means that it should be initialized with "the integral constant zero implicitly converted to T".

So, if I read correctly all of that, in the function foo above, bar.value should always be initialized with 0 and it should never be initialized with garbage, am I right?

Note: the compiler I compiled my code with is MinGW_w4 GCC 4.9.1 with (POSIX threads and dwarf exceptions) in case that helps. While I sometimes get garbage on my computer, I never managed to get anything else than zero with online compilers.


Update: It seems to be a GCC bug that the error is mine and not that of my compiler. Actually, when writing this question, I assumed for the sake of simplicity that

class foo {
    foo() = default;
};

and

class foo {
    foo();
};

foo::foo() = default;

were equivalent. They are not. Here is the quote from the C++14 standard, section [dcl.fct.def.default]:

A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

In other words, when I got garbage values, my defaulted default constructor was indeed user-provided since it was not explicitly efaulted on its first declaration. Therefore, what happened was not zero initialization but default initialization. Thanks @Columbo again for pointing out the real problem.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • 4
    [dcl.fct.def.default] / 5 "a function is user-provided if it is user-declared and **not explicitly defaulted** or deleted on its first declaration". Your constructor is not user-provided. – user657267 Nov 02 '14 at 13:42
  • You could have written `UnsignedInt value{0}` instead of reading pages and pages ... I know you know. ;-) – davidhigh Nov 02 '14 at 13:45
  • 1
    a note... value initialization STILL doesn't work on Microsoft's compiler as of VS2013 – Mgetz Nov 02 '14 at 14:08
  • "gray_code has no constructor taking an std::initializer_list so list initialization is not performed either" - actually *list-initialization* means any time that you use a brace-enclosed list to initialize anything . `gray_code bar{};` is list-initialization. – M.M Nov 02 '14 at 22:37
  • It's really icky (IMHO) that `T() {}` behaves differently to `T() = default;` – M.M Nov 02 '14 at 22:39
  • @MattMcNabb You're right. I read the article about value-initialization first and the syntax matched what I wrote so I assumed that it was value-iitialization and didn't see that lisst initialization actually came "first" in the wording. – Morwenn Nov 02 '14 at 22:54
  • Doesn't show this question, how really ugly these initializer rules are? What especially happens from c++03 to c++11, the code which was expected to do value-initialization is no more doing the same thing in c++11? – Gabriel Apr 21 '15 at 07:16
  • @Gabriel It only shows that `= default`, added in C++11, has some strange rules. The code doing value-initialization in C++03 still performs value-initialization in C++11. – Morwenn Apr 21 '15 at 07:23
  • @ Morween, thanks, so ``= default`` strange rules is only: ``A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.`` – Gabriel Apr 21 '15 at 07:47
  • @Gabriel Yeah, basically what I find strange is that it causes `= default` to behave differently depending on whether it is or not in the class declaration. – Morwenn Apr 21 '15 at 07:49
  • The behaviour above is just crude, :-) hopefully the std committee has some good reasons for this strange rule. It is a totally illogical exception (at least for me) – Gabriel Apr 21 '15 at 07:51

1 Answers1

10

So, if I read correctly all of that, in the function foo above, bar.value should always be initialized with 0 and it should never be initialized with garbage, am I right?

Yes. Your object is direct-list-initialized. C++14's* [dcl.init.list]/3 specifies that

List-initialization of an object or reference of type T is defined as follows:

  • [… Inapplicable bullet points…]

  • Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).

  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

  • […]

Your class isn't an aggregate since it has user-provided constructors, but it does have a default constructor. [dcl.init]/7:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;

  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

[dcl.fct.def.default]/4:

A special member function is user-provided if it is user-declared and not explicitly defaulted […] on its first declaration.

So your constructor is not user-provided, therefore the object is zero-initialized. (The constructor is not called since its trivial)

And finally, in case this was not clear, to zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;

  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;

  • […]


Thus either

  • Your compiler is bugged

  • …or your code triggers undefined behavior at some other point.


* The answer is still yes in C++11, though the quoted sections are not equivalent.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • I looked at the generated assembly. The default constructor has one meaningful instruction, `mov %ecx,-0x4(%ebp)`. If I understand well, it sets the address of `bar` but never sets `value`. – Morwenn Nov 02 '14 at 21:08
  • @Morwenn Of course the constructor doesn't set `value`. The implicitly generated default constructor default-initializes member objects which means no initialization is done for scalars. – Columbo Nov 02 '14 at 21:42
  • You're right, it seems that I got confused between the constructor and the intialization part. That said, the defaulted default constructor is called in my case. But nothing else near this call site seems to initialize `value` anyway. – Morwenn Nov 02 '14 at 21:47
  • @Morwenn Yes, that is because of said bug. – Columbo Nov 02 '14 at 21:47
  • I really want to believe that this is a bug, but I am unable to produce a minimal test case where `value` is default-initialized instead of zero-initialized. Thanks anyway for your help :) – Morwenn Nov 02 '14 at 21:55
  • Default-initialized means *nothing* is done for `value`. That said, you found a minimal test case yourself, didn't you? – Columbo Nov 02 '14 at 22:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64141/discussion-between-morwenn-and-columbo). – Morwenn Nov 02 '14 at 22:18