Array nested struct initialization - c++

There have been other posts regarding initialization of array of nested structs. However, following the given advice elsewhere in Stackoverflow and links to Aggregate Initialization are not helping me resolve an issue.
My context is that I am initializing a number of nested structs in the Vulkan graphics API.
However, in looking at other codes for explanation I ran across the following 'C' initialization (please note that I am adding the defined structs and the code in question)
Vulkan typedefs
typedef union VkClearColorValue {
float float32[4];
int32_t int32[4];
uint32_t uint32[4];
} VkClearColorValue;
typedef struct VkClearDepthStencilValue {
float depth;
uint32_t stencil;
} VkClearDepthStencilValue;
typedef union VkClearValue {
VkClearColorValue color;
VkClearDepthStencilValue depthStencil;
} VkClearValue;
The 'C' code in question
const VkClearValue clear_values[2] = {
[0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}},
[1] = {.depthStencil = {demo->depthStencil, 0}},
};
The 'C' represenation is legal in C99 and the latest gcc compiler.
However, as I apply this to C++ I get errors.
Going back to rules for aggregate initialization the 'C' represenation should be legal in C++17. However, on compilation I get errors to the effect: error: expected primary-expression before '.' token
Clearly I am wrong in my assumption. As such, might anyone provide guidance on how to make it legal in C++17.

Going back to rules for aggregate initialization the 'C' represenation should be legal in C++17
You assume wrongly, as you have correctly concluded. C++ has had aggregate initialisation since the first standard version, but it doesn't support all initialisations that were added in C99. For example, designated initialisers are not in C++17 at all.
As such, trivial unions can only be initialised with the first member active, like in C89. To activate another member, it must be assigned after initialisation. To do this with a const object, you can use a function.
C++20 adds designated initialisers, but even then only subset of C is allowed. Following is well-formed in C++20:
const VkClearValue clear_values[2] = {
{.color {.float32 = {0.2f, 0.2f, 0.2f, 0.2f}}},
{.depthStencil = {42, 0}},
};

This is not a valid form of aggregate initialization in c++, even though it is in c. In fact, there is a note:
Note: out-of-order designated initialization, nested designated initialization, mixing of designated initializers and regular initializers, and designated initialization of arrays are all supported in the C programming language, but are not allowed in C++.
struct A { int x, y; };
struct B { struct A a; };
struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order)
int arr[3] = {[1] = 5}; // valid C, invalid C++ (array)
struct B b = {.a.x = 0}; // valid C, invalid C++ (nested)
struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed)

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.

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.

Initializing scalars with braces

In C and C++, one can initialize arrays and structs using braces:
int a[] = {2, 3, 5, 7};
entry e = {"answer", 42};
However, in a talk from 2007, Bjarne mentions that this syntax also works for scalars. I tried it:
int i = {7};
And it actually works! What is the rationale behind allowing the initialization of scalars with braces?
Note: I am specifically not talking about C++11 uniform initialization. This is good old C89 and C++98.
What is the rationale behind allowing the initialization of scalars with braces?
int is POD. So the brace initialization is allowed in case of int (and for all build-in types), as it makes the initialization-syntax consistent with other PODs.
Also, I guess whatever rationale behind C++11 Uniform Initialization Syntax are, are also (partially) applicable to this syntax allowed by C++03. It is just C++03 didn't extend this to include non-pod types such as the standard containers.
I can see one place where this initialization is helpful in C++03.
template<typename T>
void f()
{
T obj = { size() } ; //T is POD: built-in type or pod-struct
//code
}
Now this can be instantiated with struct which begins with a suitable member, as well as any arithmetic type:
struct header
{
size_t size; //it is the first member
//...
};
f<header>(); //body becomes : header obj = { size(); }; which is fine
f<size_t>(); //body becomes : size_t obj = { size(); }; which is fine
Also note that POD, whether struct or built-in types, can also be initialized uniformly as:
header h = header(); //value-initialized
int i = int(); //value-initialized
So I believe one reason is consistency!
The rationale is not mentioned, but from a 2005 C++ Standard draft, 8.5 Initializers [dcl.init], clause 14
If T is a scalar type, then a declaration of the form
T x = { a };
is equivalent to
T x = a;
Note that the C++ 98 Standard only allows brace initializers for copy-initialization T x = { a }, and not for direct initialization T x { a }, for which only T x(a) works.
UPDATE: see also this question
C++ probably inherited this from C. In C the main reason is to have a unique initilizer syntax, in particular for a default initializer. In C the default initializer is {0}.

C (or C++?) Syntax: STRUCTTYPE varname = {0};

Normally one would declare/allocate a struct on the stack with:
STRUCTTYPE varname;
What does this syntax mean in C (or is this C++ only, or perhaps specific to VC++)?
STRUCTTYPE varname = {0};
where STRUCTTYPE is the name of a stuct type, like RECT or something. This code compiles and it seems to just zero out all the bytes of the struct but I'd like to know for sure if anyone has a reference. Also, is there a name for this construct?
This is aggregate initialization and is both valid C and valid C++.
C++ additionally allows you to omit all initializers (e.g. the zero), but for both languages, objects without an initializer are value-initialized or zero-initialized:
// C++ code:
struct A {
int n;
std::string s;
double f;
};
A a = {}; // This is the same as = {0, std::string(), 0.0}; with the
// caveat that the default constructor is used instead of a temporary
// then copy construction.
// C does not have constructors, of course, and zero-initializes for
// any unspecified initializer. (Zero-initialization and value-
// initialization are identical in C++ for types with trivial ctors.)
In C, the initializer {0} is valid for initializing any variable of any type to all-zero-values. In C99, this also allows you to assign "zero" to any modifiable lvalue of any type using compound literal syntax:
type foo;
/* ... */
foo = (type){0};
Unfortunately some compilers will give you warnings for having the "wrong number" of braces around an initializer if the type is a basic type (e.g. int x = {0};) or a nested structure/array type (e.g. struct { int i[2]; } x = {0};). I would consider such warnings harmful and turn them off.