struct initialization with in-class member initializers - c++

I'm having trouble initializing a struct that uses in-class initializers:
struct A
{
int a{};
int b{};
};
struct B
{
int a;
int b;
};
int main()
{
A a; // OK
B b{1, 2}; // OK
B b2; // OK, but b.a and b.b are undefined
A a2{1, 2}; // ERROR!
}
Here's the error I'm getting from gcc 4.7.2:
% g++ -std=c++11 test2.cc
test2.cc: In function ‘int main()’:
test2.cc:16:11: error: no matching function for call to ‘A::A(<brace-enclosed initializer list>)’
test2.cc:16:11: note: candidates are:
test2.cc:1:8: note: constexpr A::A()
test2.cc:1:8: note: candidate expects 0 arguments, 2 provided
test2.cc:1:8: note: constexpr A::A(const A&)
test2.cc:1:8: note: candidate expects 1 argument, 2 provided
test2.cc:1:8: note: constexpr A::A(A&&)
test2.cc:1:8: note: candidate expects 1 argument, 2 provided
Should this work according to the standard, or is this actually illegal? Am I abusing the use of in-class initializers? I thought the new syntax would make it so I wouldn't have to write a constructor just to do this initialization, but now it seems that I may have to resort to that old mechanism to avoid the possibility of an uninitialized structure.

You can only use braces if either
the brace content matches a constructor (not your case), or
the class is an aggregate and each brace element matches a class member.
However, a class is an aggregate if (C++11, 8.5.1/1):
it has no brace-or-equal-initializers for non-static members
which your class clearly has. So, you don't have an aggregate, either.
Either write suitable constructors, or remove the brace-or-equal-initializers.

Related

Overload resolution between two constructors from std::initializer_list

In following program, struct C has two constructors : one from std::initializer_list<A> and the other from std::initializer_list<B>. Then an object of the struct is created with C{{1}}:
#include <initializer_list>
struct A {
int i;
};
struct B {
constexpr explicit B(int) {}
};
struct C {
int v;
constexpr C(std::initializer_list<A>) : v(1) {}
constexpr C(std::initializer_list<B>) : v(2) {}
};
static_assert( C{{1}}.v == 1 );
Since only aggregate A can be implicitly constructed from int, one could expect that C(std::initializer_list<A>) is preferred and the program succeeds. And indeed it does in Clang.
However GCC complains:
error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
note: candidate: 'constexpr C::C(std::initializer_list<B>)'
note: candidate: 'constexpr C::C(std::initializer_list<A>)'
and so does MSVC:
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'C'
note: No constructor could take the source type, or constructor overload resolution was ambiguous
Demo: https://gcc.godbolt.org/z/joz91q4ed
Which compiler is correct here?
The wording could be clearer (which is unsurprising), but GCC and MSVC are correct here: the relevant rule ([over.ics.list]/7) checks only that
overload resolution […] chooses a single best constructor […] to perform the initialization of an object of type X from the argument initializer list
so the fact that the initialization of B from {1} would be ill-formed is irrelevant.
There are several such places where implicit conversion sequences are more liberal than actual initialization, causing certain cases to be ambiguous even though some of the possibilities wouldn’t actually work. If the programmer was confused and thought one of those near misses was actually a better match, it’s a feature that the ambiguity is reported.

Problem initializing array of struct{enum;union;}

I have a problem defining s_arr[10] which is supposed to be an array of struct objects, while the struct contains an enum and a union member:
enum E {ENUM_A,ENUM_B};
union U {
String s;
char c[4];
};
struct S {
E e;
U u;
};
S s_arr[10];
I have tried to solve this in different ways, but none of them worked.
Essentially I get the following compilation error:
sketch_mar16a:13:11: error: use of deleted function 'S::S()'
S s_arr[10];
^
/.../sketch_mar16a.ino:8:8: note: 'S::S()' is implicitly deleted because the default definition would be ill-formed:
struct S {
^
sketch_mar16a:8:8: error: use of deleted function 'U::U()'
/.../sketch_mar16a.ino:4:7: note: 'U::U()' is implicitly deleted because the default definition would be ill-formed:
union U {
^
sketch_mar16a:5:10: error: union member 'U::s' with non-trivial 'String::String(const char*)'
String s;
^
sketch_mar16a:8:8: error: use of deleted function 'U::~U()'
struct S {
^
/.../sketch_mar16a/sketch_mar16a.ino:4:7: note: 'U::~U()' is implicitly deleted because the default definition would be ill-formed:
union U {
^
sketch_mar16a:5:10: error: union member 'U::s' with non-trivial 'String::~String()'
String s;
^
sketch_mar16a:13:11: error: use of deleted function 'S::~S()'
S s_arr[10];
^
/.../sketch_mar16a/sketch_mar16a.ino:8:8: note: 'S::~S()' is implicitly deleted because the default definition would be ill-formed:
struct S {
^
sketch_mar16a:8:8: error: use of deleted function 'U::~U()'
/.../sketch_mar16a.ino: In function 'void __static_initialization_and_destruction_0(int, int)':
sketch_mar16a:13:3: error: use of deleted function 'S::~S()'
S s_arr[10];
^
exit status 1
use of deleted function 'S::S()'
Unfortunately, I don't understand where the error message is trying to point me to. I tried to define initializers for the involved classes, but I don't know how (or where) to define an initializer for the union.
Using google, I only found cases where a union contains a struct, but I could not derive the solution for my problem.
Can you explain me, why the "default definition would be ill-formed"? How would the default definition look like and when during compile time does it happen?
NOTE: the code is compiled for an arduino uno using the Arduino IDE
Judging by the following error messages:
sketch_mar16a:8:8: error: use of deleted function 'U::U()'
/.../sketch_mar16a.ino:4:7: note: 'U::U()' is implicitly deleted because the default definition would be ill-formed:
union U {
and
/.../sketch_mar16a/sketch_mar16a.ino:4:7: note: 'U::~U()' is implicitly deleted because the default definition would be ill-formed:
union U {
The presence of String s; in U is the source of the problem. You'll need to define the default constructor and the destructor in U and make sure you do the right thing in those functions.
The following simplified program builds on my machine.
struct String
{
String(char const*) {}
~String() {}
};
enum E {ENUM_A,ENUM_B};
union U {
U() : s(nullptr) {}
~U() {}
String s;
char c[4];
};
struct S {
E e;
U u;
};
int main()
{
S s_arr[10];
}
You'll need proper implementations of U::U() and U::~U() so that your program is well behaved.

Uniform vs legacy initialisation producing different compilation results

I alighted on this while permuting with a trivial piece of code:
struct Base0 {};
struct Base1 {};
template<typename... Ts>
struct Derived: Ts... {};
int main() {
Derived<Base0, Base1> d0 {Base0{}, Base1{}}; // OK
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
}
I thought both d0 and d1 should have resulted in a compilation error since I can't see how Derived without any matching ctor takes ctor arguments as passed and flags d0's compilation as fine.
There's probably something obvious I'm missing. What is it about the uniform initialisation that's making it pass ? Is it aggregate initialisation or something ? What's happening with the temporaries passed to the ctor ?
Using C++17 online compiler here
Edit
As asked, I'm providing a copy-paste of the spew-out:
main.cpp: In function ‘int main()’:
main.cpp:9:47: error: no matching function for call to ‘Derived::Derived(Base0, Base1)’
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
^
main.cpp:5:8: note: candidate: constexpr Derived::Derived()
struct Derived: Ts... {};
^~~~~~~
main.cpp:5:8: note: candidate expects 0 arguments, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(const Derived&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(Derived&&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided
Looks like this is a new C++17 feature of aggregate initialisation:
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
It comes with the change that a class with bases may now be an aggregate (as long as they are not virtual, private, or protected… though they don't even need to be aggregates! 😲).
Your failing case does not use aggregate initialisation, but instead attempts a good old-fashioned constructor invocation. As you've identified, no such constructor exists.

C++11 POD structure initializing error

I have confusing situation with simple code:
struct Item {
size_t span{};
};
int main() {
Item item{1}; // error is here
return 0;
}
While compiling this I have following error:
test.cpp: In function ‘int main()’:
test.cpp:8:13: error: no matching function for call to ‘Item::Item(<brace-enclosed initializer list>)’
Item i{1};
^
test.cpp:8:13: note: candidates are:
test.cpp:3:8: note: constexpr Item::Item()
struct Item {
^
test.cpp:3:8: note: candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr Item::Item(const Item&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘int’ to ‘const Item&’
test.cpp:3:8: note: constexpr Item::Item(Item&&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘int’ to ‘Item&&’
Why g++ tries to find a ctor for initializer list in this case instead of simple C-style structure object creating?
If I remove {} from size_t span{} it compiles successfully.
It also happens if I change the line to size_t span = 0 so it seems to be some initialization in declaration issue which exists since c++11.
Usign Item item{1}; means you're doing list-initialisation (of item). List initialisation is defined as follows:
if the type is an aggregate, aggregate initialisation (what you refer to as "C-style struct object creating") happens
...
if the type is a class, constructors are considered
Your class has no constructors. It is also not a (C++11) aggregate, because it contains an initialiser for a non-static data member.
Note that this restriction (member initialisers) was lifted in C++14, so Item is a C++14 aggregate and your code, while not valid C++11, is valid C++14.

Using operator new on non-pod structs + Initializer list

I was trying out some stuff allocating structs, which contain non-pod members, on the heap and initializing them using initializer lists. However, the compiler encountered an error on my code. This snippet reproduces it:
#include <vector>
struct A {
int a;
};
struct B {
int a;
std::vector<int> b;
};
int main() {
std::vector<int> some_vec;
A a = {1}; // OK
A b = A{1}; // OK
A *c = new A{1}; // OK(leaks, NP)
B d = {1, some_vec}; // OK
B e = B{1, some_vec}; // OK
B *f = new B{1, some_vec}; // Fails to compile
B *g = new B({1, some_vec}); // OK
}
(I know that leaks, I'm aware of that, it's just a test snippet)
The line pointed out fails to compile, on GCC 4.6.3, with this error:
test.cpp: In function ‘int main()’:
test.cpp:19:29: error: no matching function for call to ‘B::B(<brace-enclosed initializer list>)’
test.cpp:19:29: note: candidates are:
test.cpp:7:8: note: B::B()
test.cpp:7:8: note: candidate expects 0 arguments, 2 provided
test.cpp:7:8: note: B::B(const B&)
test.cpp:7:8: note: candidate expects 1 argument, 2 provided
test.cpp:7:8: note: B::B(B&&)
test.cpp:7:8: note: candidate expects 1 argument, 2 provided
Apparently the compiler can't initialize my struct using the provided initializer list. The strange this is that the next line after the one that produces an error, which(at far as I can see) just copies(probably moves) a B from another that was constructed using that same initializer list, does not produce any error.
Is there anything wrong I am doing? I mean, I can live using that last line on the snippet provided, but is there any reason why I can't just create a struct using operator new and an initialization list?
The code should compile and work. I tested it with gcc 4.7.0, and it works fine, so the bug has been fixed it seems. If you read 8.5.4 List-initialization in the standard, they say List intialization can be used as the initializer in a new expression (5.3.4).