Initializing const members of structs in C/C++... compiler dependent?
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 en开发者_如何学Cd 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 ) ;
}
精彩评论