Constructor interferes with member variable designated initializer? - c++

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.

Related

Why is designated initialization accepted by gcc with no effect, and generated aggregate is somehow passed as arguments to class constructor

Ok, we have this code:
#include <iostream>
using namespace std;
class A{
public:
A(double b, int g) {
cout << "b = " << b << ", g = " << g << endl;
}
};
int main()
{
A a = {.g = 5.0, .j = 10}; // output: b = 5, g = 10
A b = {.b = 5.0, .g = 10}; // output: b = 5, g = 10
A bb = { 4.0, 20 }; // output: b = 4, g = 20
A c = {.g = 5.0, .b = 10, 9}; // error: could not convert ‘{5.0e+0, 10, 9}’ from ‘<brace-enclosed initializer list>’ to ‘A’
A d = {.g = 5, .b = 10.0}; // error: narrowing conversion of ‘1.0e+1’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
A e = {.b = 10.0}; // error: could not convert ‘{1.0e+1}’ from ‘<brace-enclosed initializer list>’ to ‘A’
return 0;
}
Three first lines of main() compile, other produce error in comment.
So, experiments show, that:
.name = designated initializer has no effect. Compiler simply ignores any named field
Only order of values in {} braces matters.
Compiler generates <brace-enclosed initializer list> and looks up for constructor of class A with arguments matching elements in list by type and count, otherwise error produced.
g++ compiler 9.3 is used. Passing -pedantic flag throws warning for each value with designator in {} braces
warning: C++ designated initializers only available with ‘-std=c++2a’ or ‘-std=gnu++2a’
Passing -std=c++2a hides the warning, and gives one more error message for A c = {.g = 5.0, .b = 10, 9};:
error: either all initializer clauses should be designated or none of them should be
clang fails to compile two first lines, even with -std=c++2a, producing for each
error: no matching constructor for initialization of 'A'
note: candidate constructor not viable: cannot convert argument of incomplete type 'void' to 'double' for 1st argument
note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided
Cppreference about Designated initializers in C++ shows only cases of named initialization of class fields, but not constructor arguments. Aggregate initialization and List initialization examples also don't show such feature with constructors.
Well, as stated in the warnings, this case may be treated as C++20 feature, but looking at C++ 20 features, as well as cppreference.com for proposed features gives only designated initialization for aggregate types, but not through constructor arguments, as in my case.
Questions:
why and how does it work?
Is it ISO C++20 or GNU C++ feature?
Should state 1. be treated as gcc compiler silent bug?
A class that has a user defined constructor is not an aggregate class. If designated initialiser is used, then the class shall be an aggregate. Your program uses designated initialiser on a non-aggregate, therefore it is an ill-formed program. Compilers are allowed to not compile it and are required to diagnose the issue.
Furthermore, a designated initialiser shall name a member of the aggregate. Your program doesn't name a member in the initialiser and therefore it is ill-formed.
Is it ISO C++20 or GNU C++ feature?
Designated initialisers in general are a standard C++20 feature, and are also a GNU feature prior to C++20.
Initialising non-aggregates with designated initialisers is not a standard C++ feature. Whether it is a bug or a feature, it is implementation specific thing.
Should state 1. be treated as gcc compiler silent bug?
Lack of diagnostic message is violation of the standard.
This seems to have been fixed in trunk and all cases using designated initialisers are an error in GCC: https://godbolt.org/z/TWKob6 The latest released version 10.2 still reproduces the lack of error.

Aggregate initialization, set member pointer to same struct member

Is it possible to use aggregate initialization to make a pointer aptr point to a which is a member of the same struct ?
struct S {
int a;
int* aptr;
};
int main() {
S s = {
.a = 3,
.aptr = &a //point aptr to a
};
return 0;
}
The question is for both C and C++.
A working initialization would be:
struct S {
int a;
int* aptr;
};
int main() {
struct S s = {.a = 3, .aptr = &s.a};
printf("%d", *s.aptr);
}
Working samples:
C11 GNU
C++2a GNU
Regarding the correctness of the initialization:
For C:
The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.
For C++:
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear.
That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.
However, despite the differences we can observe, the order in which the expressions are evaluated does not seem matter in this case, since you're not actually accessing the value of s.a, just its address which is accessible at this point.
So this is a correct initialization both for C and C++.
Something to note with this code, in MSVC, there is a compilation error in C++:
use of designated initializers requires at least '/std:c++latest'
Using std:c++latest the error changes to:
designated and non-designated initializers is nonstandard in C++
However, compilers that range from clang 3.1 to clang 10.0 and gcc 4.9.0 to gcc 10.0 with C++03 to C++2a compile fine with no warnings.
Designated initializers where introduced in C++20, so it is actually correct not to accept them, as MSVC still does not accept /std:c++20, it is not possible to use them yet, it also looks like gcc and clang always provided support for these initializers.
That being said, a second solution would be:
struct S {
int a;
int* aptr;
};
int main() {
struct S s = { 3, &s.a };
printf("%d", *s.aptr);
}
This second version of initialization compiles with no issues in every compiler tested, so it's fair to assume that it is more portable.
The first version is probably more easily readable and allows for a easier identification of errors in initialization, one of the advantages of designated initializers.

Enum bitfield and aggregate initialization

The following code is accepted by clang 6.0.0 but rejected by gcc 8.2
enum class E {
Good, Bad,
};
struct S {
E e : 2;
int dummy;
};
S f() {
return {E::Good, 100};
}
Live godbolt example
The GCC complains
error: could not convert '{Good, 100}' from '<brace-enclosed initializer list>' to 'S'
Which one is correct? Where in the standard talks about this situation?
return {E::Good, 100}; performs copy list initialization of the return value. The effect of this list initialization is an aggregate initialization.
So is S an aggregate? The description of an aggregate varies depending on which version of C++ you're using, but in all cases S should be an aggregate so this should compile. Clang (and MSVC) have the correct behavior.
The fix is simple, though. Change your return statement to return the correctly typed object:
return S{E::Good, 100};
This should be well-formed and so this is a gcc bug.
We end up at aggregate initialization via [stmt.return]p2 which says:
… A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization ([dcl.init.list]) from the specified initializer list. …
then [dcl.init.list]p3.2 says:
Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]). …
At this point we may wonder if this is a narrowing conversion and therefore ill-formed but [dcl.init.list]p7 does not have any clauses that cover this case and no other cases in [dcl.init.list] apply to make this ill-formed.
We can see with a similar example which removes enum from the equation but keeps the bit-field shows neither gcc nor clang gives us a narrowing conversion diagnostic, which we expect to be the case, although this similar problem is covered by [dcl.init.list]p7.4 although not ill-formed:
struct S2 {
int e : 2 ;
int dummy ;
} ;
S2 foo( int x ) {
return {x, 100} ;
}
see it live in godbolt
As observed gcc does not seem to have a problem in other contexts i.e.
S f(E e1, int x) {
S s {e1,100} ;
return s;
}
So you do have work-arounds available.

Why can I initialize a regular array from {}, but not a std::array

This works:
int arr[10] = {};
All elements of arr are value-initialized to zero.
Why doesn't this work:
std::array<int, 10> arr({});
I get the following warning from g++ (version 4.8.2):
warning: missing initializer for member ‘std::array<int, 10ul>::_M_elems’
There are two issues one which is a matter of style and the warning.
Although it may not be obvious, aggregate initialization is happening on a temporary which is then being used as an argument to the copy constructor. The more idiomatic to do this initialization would be as follows:
std::array<int, 10> arr = {};
Although this still leaves the warning.
The warning is covered by gcc bug report: - -Wmissing-field-initializers relaxation request and one of the comments says:
[...]Surely, the C++ syntax of saying MyType x = {}; should be supported,
as seen here:
http://en.cppreference.com/w/cpp/language/aggregate_initialization
where for instance:
struct S {
int a;
float b;
std::string str;
};
S s = {}; // identical to S s = {0, 0.0, std::string};
That shouldn't warn for the reasons stated in earlier comments.
and a follow-up comment says:
My statement about zero-initialization was inaccurate (thanks), but
the general point still stands: in C you have to write ' = {0}' since
empty-braces initializer is not supported by the language (you get a
warning with -pedantic); in C++, you can write ' = {}' or 'T foo =
T();', but you don't need to write ' = {0}' specifically.
The latest versions of gcc does not produce this warning for this case, see it live working with gcc 5.1.
We can see this topic also covered in the Clang Developers lists in the thead: -Wmissing-field-initializers.
For reference the draft C++11 standard section 8.5.1 [dcl.init.aggr] says:
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 an empty initializer list (8.5.4). [
Example:
struct S { int a; const char* b; int c; };
S ss = { 1, "asdf" };
initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of
an expression of the form int(), that is, 0. —end example ]
So as this is valid C++, although as noted using {} is not valid C99. One could argue that it is only a warning, but this seems like idiomatic C++ using {} for aggregate initialization and is problematic if we are using -Werror to turn warnings into errors.
Firstly, you can use the ({}) initializer with an std::array object, but semantically that stands for direct-initialization using copy-constructor from a temporary value-initialized std::array object, i.e. it is equivalent to
std::array<int, 10> arr(std::array<int, 10>{});
And it is actually supposed to compile.
Secondly, you don't really have to go the ({}) way when you can just do
std::array<int, 10> arr = {};
or
std::array<int, 10> arr{};
The first of the two is the most syntactically similar to your int arr[10] = {};, which makes me wonder why you didn't try it at first. Why did you decide to use ({}) instead of = {} when you built the std::array version of = {} syntax?
Enough people have pointed this out as a "problem" when compiling with -Werror that I think it's worth mentioning that the problem goes away if you just double up:
std::array<int, 10> arr{{}};
Does not produce any warning for me on gcc 4.9.2.
To add a bit as to why this solves it: my understanding was that std::array is actually a class with a C array as its only member. So double up on the braces makes sense: the outer braces indicate you are initializing the class, and then the inner braces default initialize the one and only member of the class.
Since there is no possible ambiguity when there is only one variable in a class, just using one pair of {} should be reasonable, but gcc is overly pedantic here, and warns.

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 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 ) ;
}