Initializing const members of structs in C/C++... compiler dependent? - c++

Recently I ran into a compiler error in a legacy environment using Borland C++ 5.2. I had a .cpp file which included a header from some C source which I don't control. The header contained a struct definition which included const members, and the compiler complained about a "constant member in class without constructors". On investigation, this error seems to be compiler-dependent. Here's some sample code w/ results from various compilers:
#include <stdio.h>
typedef struct {
const float a;
} _floater;
int main()
{
_floater f = {5.1F};
printf("%f\r\n",f.a);
return 0;
}
Borland 5.2
E:\Projects\Scratchpad>bcc32 -P const_float.c
Borland C++ 5.2 for Win32 Copyright (c) 1993, 1997 Borland International
const_float.c:
Error const_float.c 13: Constant member ' ::a' in class without constructors
*** 1 errors in Compile ***
Microsoft VS 2003 .NET:
E:\Projects\Scratchpad>cl /TP const_float.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
const_float.c
const_float.c(19) : error C2552: 'f' : non-aggregates cannot be initialized with
initializer list
Microsoft VS 2008:
C:\Projects\Scratchpad>cl /TP const_float.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
const_float.c
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation. All rights reserved.
/out:const_float.exe
const_float.obj
C:\Projects\Scratchpad>const_float.exe
5.100000
G++ 3.3.3
$ g++ const_float.c -o const_float.exe
const_float.c:25:2: warning: no newline at end of file
$ ./const_float.exe
5.100000
Note that Borland fails at the declaration of the struct, because it has a const member but no constructors, while VS 2003 is ok w/ the declaration, but complains when you try to instantiate it with an initializer list – considering the struct a non-aggregate type. VS2008 and g++ are perfectly happy.
[Apologies.. I just realized that the line #s in the errors are wrong because I stripped some commented-out lines before posting.]
Microsoft’s definition of aggregates is here: http://msdn.microsoft.com/en-us/library/0s6730bb.aspx. It’s not apparent to me that const members would make a struct non-aggregate, but maybe they did back in 2003.
It also appears that the latest Borland (Embarcadero) compiler treats this as a warning rather than an error: http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devwin32/wrnmembnocons_xml.html .
So, 2 questions I guess:
Why the variance in compilers? Is the standard ambiguous on this point?
Any workarounds? Given that I'm stuck w/ the compiler version and the header file, I don't see any.
Thanks!

The standard is pretty clear. Having a const member doesn't bar a class from being an aggregate.
8.5.1 [dcl.init.aggr]
An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).
It is legal to copy-intialize a const object and this is the initialization that aggregate initialization performs on the members of the aggregate. The restrictions on not naming a const object with no user-declared constructor in the mem-initializer-list in 12.6.2 apply only to initialization by a constructor which doesn't apply because aggregate initialization happens instead.
As to why the older compilers fail, I don't know. I can only say that they don't conform to the standard in this respect.

Question 1:
Compilers published at different times implement different versions of C++. They are different approximations to a standard. All have vendor-specific "additions", i.e. allow non-portable code.
BCC 5.2 was created before first C++ standard (ISO 14882:1998).
VC++2003 is close to ISO 14882:1998, but also has some deficiencies, especially concerning templates.
VC++2008 nearly implements ISO 14882:2003.
Question 2:
Try to get a modern compiler to your legacy system, if it's 32bit Windows.
Try to compile on a modern machine and deploy the executable on your legacy system.
If the legacy system is 16bit Windows, I don't see a solution.

On g++ 4.6.1 on the same Linux machine, -Wall -ansi -pedantic does not bring up any warnings.
This point might not have been so high on compiler writers' agenda. It looks so to me, looking at VS2003's and VS2008's behaviours, and, looking at g++ 3.3.3's behaviour you posted and g++ 4.6.1's behaviour I observed.
Can you consider changing the const to a private:, non const? That way, you will still have some control over who writes to it by not exporting a setter for it while, not generating compiler errors.

Just to add some extra information on the subject for MSVC and G++, and echoing exactly what #Charles Bailey, and #René Richter said; Its acceptable C++03, and C++11 but depending on the age and implementation state of your compiler you are going to get differing errors if at all.
Actually I tried Dinkum's online test compilers for EDG, MSVC2008, and G++ and all compile the sample you gave but NOT the sample I give below.
So in short fully initialized structs with const members will successfully compile on MSVC2008 and G++4.5, however of the tested compilers none can compile "partially initialized" (or partially aggregate initialized) POD structures even though that too is allowable in the C++ standard - I even contacted some G++ bug maintainers to make sure I was reading that standard correctly and they confirmed it should work even in current C++03 compilers.
You can see related bugs for this on both GNU Bugzilla and over at Microsoft's Visual Studio help pages which was actually linked from this other stackoverflow article titles "why do I get this warnings in Visual Studio when building a struct? which is also related to Microsoft's Error C3852 as a known behavior of even MSVC2010
// All sections refer to Draft C++03 (brackets refer to draft C++11)
//
// 3.9.3 CV (const/volatile) definition as "const data-type [= optional init]"
// 7.1.5.1/1 The cv-qualifiers [ 7.1.6.1/1 in C++11 ]
// "init-declarator-list of the declaration shall not be empty"
const int constval = 10 ;
// HOWEVER:
// 7.1.5.1/2 The cv-qualifiers [ 7.1.6.1 in C++11 ]
// [Note: as described in 8.5, the definition of an object or subobject
// of const-qualified type must specify an initializer or be subject to
// default-initialization. ]
// 8.5 Initializers
// 8.5/9 (C++11 8.5/11)
// Otherwise, if no initializer is specified for a non-static
// object, the object and its sub-objects, if any, have an indeterminate
// initial value(*90); if the object or any of its sub-objects are of
// const-qualified type, the program is ill-formed.
//
// *90: This does not apply to aggregate objects with automatic storage
// duration initialized with an incomplete brace-enclosed initializer list
// see 8.5.1.
// [ C++11 this sub-clause has been removed, however the list-initializer section
// pretty much covers the same topic - see 8.5.1/7 below ]
//
// 8.5.1 Aggregate definition
// 8.5.1/7 (C++11 8.5.1/7)
// If there are fewer initializers in the list than there are members in the
// aggregate, then each member not explicitly initialized shall be
// value-initialized (8.5).
//
// 8.5/5 value initialization
// if T is a class type (clause 9) with a user-declared constructor
// (12.1), then the default constructor for T is called (and the
// initialization is ill-formed if T has no accessible default constructor)
// ...
// otherwise, the object is zero-initialized
//
// 8.5/5 zero initialization
// if T is a scalar type (3.9), the object is set to the value of 0 (zero) converted to T;
//
// POD type
struct A {
int n ;
const int m ; // "const" causes failure in MSVC to make default constructor
} ;
// Example of non-POD
struct B {
int bbb ;
B(){}
} ;
#include <stdio.h>
int main() {
// C++03/11 ill formed code, fails as expected
const int c0 ; // per 7.1.5.1 "not allowed to be default init"
// OK
const int c = *new int ; // A default initialized constant
const int c2 = *new int(); // A zero-init, above is DEFAULT-INIT
printf( "c: %i\n", c ) ; // should be an undef-value
printf( "c2: %i\n", c2 ) ; // should be 0
// OK ; Array example making sure it works
const int aa[5] = {}; // all items value->zero-initialized per the above 8.5.1/7
printf( "aa: %i %i %i\n", aa[0], aa[2], aa[4] ) ;
// C++03/11 ill formed code, no initializer (G++/MSVC should fail)
A a0 ; // Correct error - no default constructor or initializer (8.5/9)
// C++03/11 correctly formed code, full initializer list (G++/MSVC should pass)
A a1 = {1,2}; // Explicit initialization OK, G++/MSVC pass
// C++03/11 correctly formed code; copy initialized from a value-initialized A()
A a2 = A(); // G++ OK, MSVC FAIL
// C++03/11 correctly formed code; aggregate partial intializer (8.5.1/7 agg list init)
A a3 = {}; // G++/MSVC FAIL
A a4{}; // C++11 only - doesnt work in G++ (didnt try MSVC2010)
printf( "a0.m=%i\n", a0.m ) ; // a0 should not exist due compile errors
printf( "a1.m=%i\n", a1.m ) ; // a1.m should be 2
printf( "a2.m=%i\n", a2.m ) ; // a2.m should be 0
printf( "a3.m=%i\n", a3.m ) ; // a3.m should be 0
// C++03/11 correctly formed code; user-default constructor supplied.
const B bee1 ; // Default constructor marks bbb as "initialized"
const B bee2 = {} ; // CORRECTLY flagged error; init of non-aggregate
printf( "%i\n", bee1.bbb ) ;
}

Related

Why does list-Initialized object using default constructor compile in gcc9 but not in gcc5.1?

Why does following code compiles in gcc9 but not in gcc5.1?
struct AAA {
int xxx = 1;
};
int main() {
AAA p;
new AAA{p};
}
Error when compiled with gcc5.1 -
/home/genstor/cpp/test.cpp:11:18: error: cannot convert 'AAA' to 'int' in initialization
new AAA{p};
^
What have I found so far?
I have seen that using list-initializer for structs where default constructor's is used have some problems compiling in 4.8.1 from here, but couldn't relate it with this as it is 5.1. Any help in understanding this more is appreciated.
CMD: g++ ~/cpp/test.cpp --std=c++14
Repro link - https://godbolt.org/z/hEz95dq4G
gcc5.2 resolves issue 51747
which relate to a standard defect reports #1467
Change 8.5.4 [dcl.init.list] paragraph 3 as follows:
List-initialization of an object or reference of type T is defined as
follows:
If T is a class type and the initializer list has a single element of
type cv T or a class type derived from T, the object is initialized
from that element.
Otherwise, if T is an aggregate...
I'm not sure why c++11 compiles though. the bug report for gcc seems to indicate it would not compile either.

C++ "volatile struct" issue: compiles with gcc 11 but not with earlier versions

The following code compiles using gcc 11+ but fails to compile with gcc version <= 10.
#include <stdint.h>
typedef volatile struct s {
uint32_t receiver:1;
uint32_t transmitter:1;
uint32_t _res:30;
} s_t;
s_t get_struct(void){
struct s m = {0};
return m;
}
int main() {
// compiles using gcc 11
// does not compile using gcc 10 and lower
s_t io1 = get_struct();
return 0;
}
You can try for yourself here:
https://godbolt.org/z/cavYxKdKo
Can you please explain why this is?
FYI, the code compiles with earlier gcc version if the individual struct members are qualified with volatile (instead of the struct itself). I don't know why this is as I thought the semantics are the same (at least they are in C).
typedef struct s {
volatile uint32_t receiver:1;
volatile uint32_t transmitter:1;
volatile uint32_t _res:30;
} s_t;
Similar questions:
volatile struct = struct not possible, why?
Copy constructor for C volatile bitfield struct
Thanks to helpful comments to my question, I try to answer the question myself with the best of my knowledge. Please comment if you spot mistakes.
Whether the code compiles or not has to do with the C++ version used.
The given code
compiles with C++17 (use gcc 11 or gcc 10 with option -std=c++17) and
does not compile with C++14 (use gcc 10 or gcc 11 with option -std=c++14).
The difference between both C++ versions with regard to this issue is based on "mandatory elision of copy/move operations" starting with C++17, see here.
Using C++14, appropriate constructors need to be defined in order to construct s_t io1.
To see this more easily, in the linked example switch to "x64-64 clang 13.0.0)". This compiler provides diagnostic messages that are easier to comprehend in my opinion. "clang" compiles the code when given the option -std=c++17. Otherwise, it emits error messages:
error: no matching constructor for initialization of 's_t' (aka 'volatile s')
It also lists several implicitly defined constructors that were rejected as "not viable".
In other words, we need to provide the proper constructor manually (aka. user-defined constructor).
First, we need to take care of the initialization of struct s m = {0}; because as soon as we add any user-define constructors, there won't be an implicitly defined constructor any more. See default constructor. Further, aggregate initialization does not apply any more as soon as there are user-defined constructors added to the struct.
This will do s(int i) {} for compiling (let's omit initialization or other code from constructors).
Second, we will need to provide a constructor for argument type s_t.
If I declare a constructor s(volatile struct s l) {}
"clang" complains with
error: copy constructor must pass its first argument by reference
s(volatile struct s l) { }
If I declare a copy constructor s(volatile struct s & l) { }
"clang" complains with
error: no matching constructor for initialization of 's_t' (aka 'volatile s')
However, if I declare a move constructor s(volatile struct s && l) { }
the code compiles (with clang and gcc, C++14 and C++17).
This is the resulting code to try for yourself.

Constructor interferes with member variable designated initializer?

For a while now, one has been able to use "designated initializer" in GCC:
struct CC{
double a_;
double b_;
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.bla = 0., .bli = 0.}; // compile error
However when I add a constructor the labels are ignored.
struct CC{
double a_;
double b_;
CC(double a, double b) : a_{a}, b_{b}{}
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.b_ = 2., .a_ = 1.}; // compiles but labels don't matter only the order, confusing
CC cc{.bla = 2., .bli = 1.}; // compiles but labels don't matter, confusing
In other words the initializer syntax with a constructor make the label behave just as a comment!, which can be very confusing, but above all, it is very odd.
I discovered this accidentally, with gcc 8.1 -std=c++2a.
Is this the expected behavior?
Reference: https://en.cppreference.com/w/cpp/language/aggregate_initialization
EDIT 2022: Now that compilers support -std=c++20 the behavior is correct. All the GCC version that accept -std=c++20 (10.1 and above) also accept the above code or give an error when it should.
https://godbolt.org/z/h3eM3T7jK
Labels for designated initializers should not be ignored by compilers. All three of those examples with a constructor should be ill-formed.
You are likely getting this behavior due to a conflict between the C++20 designated initializer feature and GCC's C-style designated initializers which you are implicitly accessing due to GCC just giving them to you. If GCC were properly C++20-compiliant, once you gave the type a constructor, it would cease to be an aggregate and thus designated initializer usage would be ill-formed.
Basically, this is a driver bug caused by non-C++-standard behavior the compiler gives you by default. Odds are good that if you turn off this feature, you would get a proper compiler error for those cases.
This is a gcc bug, this still builds even with -pedantic in which we should receive warnings for any extensions
...to obtain all the diagnostics required by the standard, you should also specify -pedantic ...
and gcc claims to support the P0329R4: Designated initializers proposal for C++2a mode according to the C++ Standards Support in GCC page:
Language Feature | Proposal | Available in GCC?
...
Designated initializers | P0329R4 | 8
In order to use Designated initializers the type should be aggregate [dcl.init.list]p3.1:
If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. The ordered
identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered
identifiers in the direct non-static data members of T. Aggregate initialization is performed (11.6.1).
[ Example:
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error: designator order does not match declaration order
A b{.x = 1, .z = 2}; // OK, b.y initialized to 0
—end example ]
CC is not an aggregate according to [dcl.init.aggr]:
An aggregate is an array or a class (Clause 12) with
- (1.1) — no user-provided, explicit, or inherited constructors (15.1),
....
gcc bug report
If we look at gcc bug report: Incorrect overload resolution when using designated initializers we see in this given example:
Another test case, reduced from Chromium 70.0.3538.9 and accepted by
clang:
struct S { void *a; int b; };
void f(S);
void g() { f({.b = 1}); }
This fails with
bug.cc: In function ‘void g()’:
bug.cc:3:24: error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
void g() { f({.b = 1}); }
^
The error suggests the field names are simply ignored entirely during
overload resolution, which also explains the behaviour of the
originally reported code.
It seems gcc ignores field names during overload resolution. Which would explain the strange behavior you are seeing and that we obtain correct diagnostics when we remove the constructor.

Clang Compile error with default initialization [duplicate]

This question already has answers here:
Do I really need to implement user-provided constructor for const objects?
(4 answers)
Closed 8 years ago.
Consider following example:
#include <iostream>
#include <type_traits>
struct A
{
//A() = default; // does neither compile with, nor without this line
//A(){}; // does compile with this line
int someVal{ 123 };
void foobar( int )
{
};
};
int main()
{
const A a;
std::cout << "isPOD = " << std::is_pod<A>::value << std::endl;
std::cout << "a.someVal = " <<a.someVal << std::endl;
}
See Live example
This does compile with g++ but does not compile with clang++, tried with following command: clang++ -std=c++11 -O0 main.cpp && ./a.out
Compile error from clang:
main.cpp:19:13: error: default initialization of an object of const type 'const A' requires a user-provided default constructor
I learned from This Stack Overflow Question, that non-POD classes get default constructor. This is even not necessary here because the variable has c++11-style default initialization
Why does this not for clang?
Why does A() = default; not work, too?
This is addressed in CWG issue #253 which discusses the need for a user provided constructor for empty objects or objects whose subobjects are fully initialized (which is the case in your example).
Quoting part of the linked issue
Notes from the August, 2011 meeting:
If the implicit default constructor initializes all subobjects, no initializer should be required.
Technically it is an active issue but given that note it seems likely that it'll be resolved the way gcc chose to implement it.
Clang, on the other hand, has chosen to wait until the issue is resolved before implementing a solution.
In Clang, we're waiting for the issue to actually be resolved before we take a direction on it.
So, as it currently stands, clang is correct.
You quoted the answer yourself. In the SO answer that you linked, there is the following quote from the standard (section 6.8.6precisely):
If a program calls for the default initialization of an object of a
const-qualified type T, T shall be a class type with a user-provided
default constructor.
emphasis mine. The line
A() = default;
obviously does not provide a constructor, it does the opposite by telling the compiler that you don't want to provide one, thus your code doesn't compile. However, once you provide the constructor by uncommenting
A(){};
it works fine. So, to summarize, the error that clang shows is per standard, and the behaviour of gcc is probably a bug.

Value initialization and Non POD types

An hour ago I posted an answer here which according to me was correct. However my answer was downvoted by Martin B. He said
You're just lucky and are getting zeros because the memory that i was placed in happened to be zero-initialized. This is not guaranteed by the standard.
However after reading Michael Burr's answer here and trying the following sample code
1)
#include <cassert>
struct B { ~B(); int m; };
int main()
{
B * b = new B();
assert(b->m == 0);
}
I got a debug error on MSVC++ 2010.
I got a similar error when I tried the following code [My answer here] on MSVC++2010
2)
#include <cassert>
struct Struct {
std::string String;
int Int;
bool k;
// add add add
};
struct InStruct : Struct
{
InStruct() : Struct() {}
};
int main()
{
InStruct i;
assert(i.k == 0);
}
Neither (1) nor (2) gave any such error on gcc/Clang which made me think if MSVC++2010 does not support C++03. I am not sure.
According to Michael Burr's post [in C++03]
new B() - value-initializes B which zero-initializes all fields since its default ctor is compiler generated as opposed to user-defined.
The Standard says
To value-initialize an object of type Tmeans:
— if T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if Thas no accessible default constructor);
.....
otherwise, the object is zero-initialized
From the first point if there is no user declared default constructor the compiler synthesized default constructor will be called which will zero initialize all the fields (according to last point).
So where am I wrong? Is my interpretation of value initialization correct?
Visual Studio has known bugs in all current versions (2005, 2008, 2010) where it doesn't correctly implement value-initialization for non-POD types that don't have a user declared constructor.
By the language rules none of you asserts should fire but do exhibit the compiler issues. These are some of the bug reports, note that they are all closed or resolved as "Won't Fix".
http://connect.microsoft.com/VisualStudio/feedback/details/564268/c-value-initialization
http://connect.microsoft.com/VisualStudio/feedback/details/484295/vc-does-not-value-initialize-members-of-derived-classes-without-user-declared-constructor
http://connect.microsoft.com/VisualStudio/feedback/details/100744/value-initialization-in-new-expression
For people who stumble upon this question in 2015, like me:
All of the above issues have been fixed in VS 2015. Value initialization now works as defined in the standard.