Aggregate initialization of anonymous struct with designated initializers - c++

I am porting legacy C++ code to work with GCC 9.2. Using C++20 and GNU extensions are valid options.
The legacy code makes heavy use of anonymous structs nested in unions and aggregate initialization with designated initializes for example:
union u
{
int a;
struct
{
int b;
int c;
};
};
u f = { .b = 1, .c = 2 };
This example does compile with clang -std=gnu++2a -Wall -Wextra, but it does not compile with g++ -std=gnu++2a -Wall -Wextra:
error: too many initializers for 'u'
As there are many cases where such constructs are applied in the code, it would be necessary to apply potential changes to the code in an automated way (for example with the help of regular expressions). How can I compile "this code" with GCC 9.2 by changing the code in an automated way and as little as possible?

By moving the nested structure to the first position within the union and initializing the structure like an non-anonymous structure compiles with g++ -std=gnu++2a -Wall -Wextra:
union u {
struct
{
int b;
int c;
};
int a;
};
u f = { {.b = 1, .c = 2 } };
It should be possible to detect all anonymous structs within unions with regular expressions in the union's definitions. But I do not see how regular expressions can be used to modify the list initialization appropriately.

You can initialize only one member of the union:
because of 15.6.2 [class.base.init] paragraph 8:
An attempt to initialize more than one non-static data member of a union renders the program ill-formed.
so if you put: { .b=1, .c=2 } it sees you are initializing two members.
Then you can initialize either, .b or .c but not both.
Moreover:
If the element is an anonymous union object and the initializer list is a designated-initializer-list, the anonymous union object is initialized by the designated-initializer-list​​{​​D​​}​, where ​D​ is the ​designated-initializer-clause​ naming a member of the anonymous union object. There shall be only one such designated-initializer-clause​.
The key is: where ​D​ is the ​designated-initializer-clause​ naming a member of the anonymous union object
You may get more info here.
To be able to give a value to the members of the struct you should give an element:
union u
{
int a;
struct
{
int b;
int c;
} v; // <-- this
};
u f = { .v = { .b = 1, .c = 2 } };
But this would break the accesses to every u: i.e. f.v.b = 0.
So I think your construct is not compatible with C++20. Why clang compiles and works? May be it is an extension when there are no constructors and/or destructors involved.

Related

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.

C++ proper way to inline initialize member variables

Given the example code:
struct S {
char data[5];
int a;
};
When running the "Run code analysis" in Microsoft Visual Studio, It warns to initialize all variables.
Now I know you can do this a number of ways, create a default constructor such as:
S() :
data{0},
a{0} {
}
That makes the warning go away. But what if you don't want to manually create the default constructor.
something like:
struct S {
char data[5];
int a = 0;
};
gets rid of the warning for a but not data, though you can fix that by adding {} after like so: char data[5]{}; this seems to make the code analysis happy.
That got me thinking, you can also initialize a like int a{0};
So my question is, are these all valid, and which is preferred?
Side note: I noticed std::array has _Ty _Elems[_Size]; which it never initializes anywhere, nor does it have {} after it, I'm assuming they just ignore this warning? Or are they doing something I'm not noticing to "fix" the warning?
Also wanted to add that this code:
#include
#include
template<class T, std::size_t N>
struct static_vector {
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N] = {0};
T& operator[](std::size_t pos) {
return *std::launder(reinterpret_cast<T*>(&data[pos]));
}
};
int main(int argc, char**) {
static_vector<int, 10> s;
s[0] = argc;
return s[0];
}
under gcc9.1 -std=c++17 -Wall produces no warnings,
yet the same code under clang8.0 -std=c++17 -Wall gives me:
warning: suggest braces around initialization of subobject [-Wmissing-braces]
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N] = {0};
^
{}
I see that I can set it to = {}; which fixes it, just wondering why one compiler would produce a warning when the other doesn't? Which one is to spec?
The guideline from CPPCoreGuidelines on this states: Don’t define a default constructor that only initializes data members; use in-class member initializers instead
So you can just do:
struct S {
char data[5] = {0};
int a = 0;
};
As to your other question about the lack of warning related to std::array, GCC has a note which states:
Warnings from system headers are normally suppressed, on the assumption that they usually do not indicate real problems and would only make the compiler output harder to read.
I believe this would be true of MSVC as well.
In C++ for each declarator, the initializer may be one of the following:
1. ( expression-list )
2. = expression
3. { initializer-list }
The description for these are as follows:
comma-separated list of arbitrary expressions and braced-init-lists in parentheses
the equals sign followed by an expression
braced-init-list: possibly empty, comma-separated list of expressions and other braced-init-lists
Well which type of initialization to prefer actually depends upon context. To initialize data members in a class I personally prefer in class initialization using braced initializer, as in that case we don't have to write a user defined default constructor, compiler generated one is always efficient.
Class members
Non-static data members can be initialized with member initializer
list or with a default member initializer.
In your case you can probably use:
struct S {
char data[5] = {0}; //initialize by zero
int a = 0;
};
or to give them different values also:
struct S {
char data[5] = {0,1,2,3,4};
int a = 0;
};
For more info see Initialization

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.

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.

Templated unions in c++11

Does the c++11 standard say anything about templated unions? (I can't find anything in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf, but I haven't read it that carefully.)
I have
template<typename T>
union u {
T a;
char b;
};
template<typename T>
u<T> make_u(T t) {
return { .a = t };
}
int main() {
return make_u<int>(1).a;
}
This code causes icpc -std=c++11 to say error: a designator into a template-dependent type is not allowed, g++ -std=c++0x to say error: expected primary-expression before ‘.’ token, and g++ -std=c++11 (version 4.8.0 (experimental)) to say internal compiler error: in lookup_field_1, at cp/search.c:387. I can get around this by replacing { .a = t } with t. However, I could not do this for fields which are not the first member of the union. Is there a way to pick some member other than the first one in a templated union, where the relevant member is template-dependent? (I could, of course, declare a union on the stack, and set the member equal to the value I want. But I could not do this in an initializer list or in a constexpr function.)
The { .a = t } syntax is a non-standard GNU extension, so its interaction with other C++ features is outside the scope of the C++ standard.
Solution: write standard C++:
u<T> make_u(T t) {
u<T> r;
r.a = t;
return r;
}
EDIT: AFAIK, in C++11, you can give your union a constructor (a constexpr if you like) that does the initialization you need. Example: http://ideone.com/s4GHjU