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.
As default constructor initializes the data members of class to 0.
In C programming language, the variables should be declared before a value is assigned to it. In an array, if fewer elements are used than the specified size of the array, then the remaining elements will be set by default to 0. Let us see another example to illustrate this.
std::array::array For elements of a class type this means that their default constructor is called. For elements of fundamental types, they are left uninitialized (unless the array object has static storage, in which case they are zero-initialized).
Instance variables of numerical type (int, double, etc.) are automatically initialized to zero if you provide no other values; boolean variables are initialized to false; and char variables, to the Unicode character with code number zero.
So, if I read correctly all of that, in the function
foo
above,bar.value
should always be initialized with0
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 literal0
(zero) toT
;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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With