10
votes

This code compiles in Coliru with warnings [unitialized members a[1].i and a[2].i in the std::cout << expression in main()] but compiles normally in Ideone.

#include <iostream>

struct A
{
    int i;
    A(int j) : i{j} {};
    A() = default;
};

int main() {
    A a[3] = { A(1) };
    std::cout << a[1].i << ' ' << a[2].i << '\n';
}

According to my interpretation of iso § 8.5 p7, Ideone is correct, because of the 4th bullet point in this clause.

This is § 8.5 p7 from N3797

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;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object’s initialization.

3
Coliru is correct (although you didn't specify which compiler was used).chris
Hold on, I'm probably wrong.chris
Warnings vary from compiler to compiler, none of them are right or wrong.olevegard
@chris my money (not much, granted) would be on ideone being right about this. I expect a value initialization of the other As in the array to value initialize the corresponding is.juanchopanza
@juanchopanza, Yeah, it's the whole thing of int a[3] = {1}; leaving a[1] and a[2] as 0, but with a class instead, and for some reason, that's the one rule you get taught in beginner classes that I always forget at first.chris

3 Answers

8
votes

There's a distinct difference in the behavior of value-initialization between C++11 and N3797 for classes with both a defaulted default constructor and another non-default constructor. C++11 § 8.5/7:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
  • if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

N3797 § 8.5/8:

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;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

Your struct A has a user-declared default constructor A() = default; and a user-provided non-default constructor A(int j) : i{j} {}. In C++11, it's subject to the first bullet: it has a user-provided constructor, so the default constructor is called (which does nothing: A's default constructor is trivial). In N3797, the second bullet applies since A is "without a user-provided or deleted default constructor" so the object is zero-initialized.

Put simply, value-initialization in C++11 of an object of a class with any user-provided constructor will not perform zero-initialization before default-initialization. In N3797, value-initialization of an object of a class with no user-provided default constructor will perform zero-initialization before default-initialization.

It appears that the version of clang on Coliru has been tracking the standard here post-C++11, but GCC 4.8 has not.

EDIT: This test program demonstrates that GCC 4.8 actually does follow the N3797 rules for value initialization. The problem seems to be that it is default-initializing the array elements for which no initializer is provided instead of value-initializing them as required by the standard. Note the difference in behavior between the second array element, which is explicitly provided an empty initializer, and the third which is provided no initializer.

This is looking like a probable GCC bug.

EDIT: The same test program compiled by the same GCC version on Ideone doesn't demonstrate the bug. No idea what is going on here. Perhaps different compiler flags affecting the output on Ideone, I have no idea how to determine the compiler command line used.

9
votes

Ideone is correct as far as C++14 (N3797) is concerned (see Casey's answer for C++11) because a[1] and a[2] are initialized with A{}, which is value initialization, causing i to be 0. This comes from N3797 § 8.5.1/7:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal initializer, from an empty initializer list (8.5.4). [ Example:

struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };

initializes ss.a with 1, ss.b with "asdf", ss.c with the value of an expression of the form int{} (that is, 0), and ss.d with the value of ss.b[ss.a] (that is, ’s’)

An array is an aggregate per § 8.5.1/1 (An aggregate is an array...), so this applies to the array's initialization.

The expression T{} (an empty initializer list) value-initializes the object per § 8.5.4 /3:

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

We can confirm that the value-initialization leaves i with a value of 0 with § 8.5/8:

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;

Your class has no user-provided default constructor per § 8.4.2/4:

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

An interesting point to note is that if your default constructor was user-provided and did not initialize i, the first point of 8.5/8 would be used and i would be left uninitialized, as you can see in this example.

Finally, a small note on the comparison. Ideone uses a few different versions of GCC. Which one is used makes a difference (you can check with __VERSION__ if you need to). The compiler flags also make a slight difference in this case. If -std=c++1y is present (and I don't know a way to check that other than using an added feature), there will be some C++14 support, but not complete support, so the small changes (T{} vs. T(), initializing from a brace-or-equal-initializer and checking the semantic constraints for default-initialization) might not be implemented. In fact, you can even check the first. Coliru lets you configure the build command, so just saying Coliru is extremely ambiguous.

Either way, using N3797 to test for conforming behaviour isn't too worthwhile until enough C++14 support comes along (or at least until it's standardized). I'm tending to stick with N3485 until that happens. In this specific example, I don't think there is any difference between the two standards' behaviours. Check Casey's answer for how the two standards differ in this matter. You have a conversion constructor, so your objects would be default-initialized in C++11.

4
votes

The default compilation command for Coliru is:

g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

It uses -Wall which enables a wider range of warnings. To be pedantic, it enables the following warnings:

  -Waddress   
  -Warray-bounds (only with -O2)  
  -Wc++11-compat  
  -Wchar-subscripts  
  -Wenum-compare (in C/ObjC; this is on by default in C++) 
  -Wimplicit-int (C and Objective-C only) 
  -Wimplicit-function-declaration (C and Objective-C only) 
  -Wcomment  
  -Wformat   
  -Wmain (only for C/ObjC and unless -ffreestanding)  
  -Wmaybe-uninitialized 
  -Wmissing-braces (only for C/ObjC) 
  -Wnonnull  
  -Wopenmp-simd 
  -Wparentheses  
  -Wpointer-sign  
  -Wreorder   
  -Wreturn-type  
  -Wsequence-point  
  -Wsign-compare (only in C++)  
  -Wstrict-aliasing  
  -Wstrict-overflow=1  
  -Wswitch  
  -Wtrigraphs  
  -Wuninitialized  
  -Wunknown-pragmas  
  -Wunused-function  
  -Wunused-label     
  -Wunused-value     
  -Wunused-variable  
  -Wvolatile-register-var 

More informations about those can be found here.

Even though, Ideone uses GCC 4.8 as well, chances are it doesn't set -Wall. As you can see, without that flag, no warnings are risen on Coliru too.

To be explicit: they are literally the same compiler and with the same flags they behave exactly the same, therefore they are both correct.