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.
Related
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.
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.
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.
After reading up on list-initialization and its various flavours, I decided to test out some features in an openGL ES app I'm writing using Xcode and Objective-C++, until I ran into something rather obscure.
I've known about (and frequently implement) the conventional C style structure initialization using the following syntax to initialize POD, such as a GLKVector2, for example:
GLKVector2 point = { 0,0 }; //copy-initialized
but this approach may not always be what's intended by the programmer. So, by removing the assignment (and a needless copy construction operation), in favour of direct-initilization, one would assume (from the documentation) the above declaration would appear like so:
GLKVector2 point{ 0,0 }; //direct-initialized, but generates compile error
However, the compiler doesn't complain when the code looks like this:
GLKVector2 point{ { 0,0 } };
To me, this appears as point is being direct-initialized from a temporary created from the inner structure { 0,0 } and thus not offering any advantage over the first approach; the temporaries still have to be allocated and deallocated.
Or perhaps this issue is simply the nature of the union/struct layout used by GLKit types confusing the compiler.
Some clarification on this odd syntax, before further implementation in the code, would be much appreciated
The outer braces delimit the initializer for the object itself, the inner braces are the initializer for a member inside the object, e.g.
GLKVector2 v = { initializers-for-members };
where initializers-for-members is { 0, 0 } because the type has an array member, of two elements, and you initialize an array of two members with { a, b }.
C++ supports "brace elision" (8.5.1 [dcl.init.aggr] paragraph 11) which means nested braces can be left out of the initializer under certain circumstances, but in C++11 brace elision is only allowed for copy-initialization. This is why you don't need two sets of braces for the copy-init case, but do need them for direct-init.
Since C++11 was completed the rules have been changed by DR 1270 to allow brace elision in the direct-list-initialization case too, but that's a change made to the post-C++11 draft and not widely supported yet.
According to docs GLKVector2 is:
union _GLKVector2
{
struct { float x, y; };
struct { float s, t; };
float v[2];
};
typedef union _GLKVector2 GLKVector2;
That's why you need double braces, you're initializing by initializing a union member. Member-wise aggregate initialization occurs in-place. If it were a flat structure, your single-braces assumption would work. This also happens when initializing an std::array (e.g. std::array<int, 2> a{{1, 2}}) because it's a POD aggregate, and no temporary C arrays are involved.
You should look at aggregate initialization.
EDIT
Actually, looking up the docs, there're rules about brace elision (only allowed in copy-initialization context) that may be of interest.
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}.