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).
Related
I understand that C++20 has modified the definition of aggregate.
In the following code, we have a Packet class with just a fixed-size array of bytes (more than 4 in the real code). It must match the binary representation, including for arrays and vectors of Packet. So, we cannot define some higher level of abstraction, just keep the low-level representation. And, because we manipulate constant packets, it must be initialized using some constant list of byte literals.
#include <cstdint>
class Packet
{
public:
uint8_t b[4];
Packet() = default;
Packet(const Packet& p) = default;
};
Packet p {{1, 2, 3, 4}};
Up to C++17, the class is an aggregate and can be initialized as in instance "p" or using "p = {{...".
With C++20, this is no longer possible because of the default constructors (we need at least the copy constructor).
See the various compiler errors:
==== gcc -std=c++17
==== clang -std=c++17
==== gcc -std=c++20
init.cpp:18:23: error: no matching function for call to ‘Packet::Packet(<brace-enclosed initializer list>)’
18 | Packet p {{1, 2, 3, 4}};
| ^
init.cpp:13:5: note: candidate: ‘constexpr Packet::Packet(const Packet&)’
13 | Packet(const Packet& p) = default;
| ^~~~~~
init.cpp:13:26: note: no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const Packet&’
13 | Packet(const Packet& p) = default;
| ~~~~~~~~~~~~~~^
init.cpp:12:5: note: candidate: ‘constexpr Packet::Packet()’
12 | Packet() = default;
| ^~~~~~
init.cpp:12:5: note: candidate expects 0 arguments, 1 provided
==== clang -std=c++20
init.cpp:18:8: error: no matching constructor for initialization of 'Packet'
Packet p {{1, 2, 3, 4}};
^ ~~~~~~~~~~~~~~
init.cpp:13:5: note: candidate constructor not viable: cannot convert initializer list argument to 'const Packet'
Packet(const Packet& p) = default;
^
init.cpp:12:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Packet() = default;
^
1 error generated.
I have tried various ways to declare an additional constructor using a std::initializer_list<uint8_t> parameter without finding the right way to initialize the field "b" from this parameter.
I have seen similar reports on SO and other sites without a working solution in the case of an array field.
Ideally, I would like to find an initialization syntax which works for all levels of standards. If a specific constructor needs to be #ifdef'ed on C++20, this is acceptable. However, there are too many initializations of Packet instances to have distinct syntaxes from C++11 to C++20 (pre-C++11 is not supported by the application).
Any idea?
Thanks in advance.
Don't explicitly default things you don't have to. There is no reason to default either of those constructors; you'll get them either way.
It is the presence of an explicitly defaulted constructor that stops it from being an aggregate. Remove those, and your code works just fine on all C++ versions post-11.
In case your actual implementation is not defaulting your constructors and you can't use Nicol's answer, you can create an array constructor like this to keep the rest of the code identical:
Packet(const decltype(b)& a)
{
memcpy(b, a, sizeof a);
}
Working Example
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.
Let's consider this class:
class A {
public:
A() = delete;
A(int i) :
i_m(i) {
std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
}
~A() {
std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
}
private:
int i_m{1234567890};
};
The default constructor is explicitly deleted so AFAIK A can only be constructed from an integer. The default initialization of data member i_m will never be used.
Let'ts consider this program:
int main() {
using T = std::array<A, 2>;
//T a;
// error: use of deleted function 'std::array<A, 2>::array()'
// note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed
// error: use of deleted function 'A::A()'
//T b{};
// error: use of deleted function 'A::A()'
}
Again, this seems completely fine to me.
Let's now consider this other program:
int main() {
using T = std::array<A, 2>;
auto foo = new T{};
delete foo;
auto foo_init = new T{1, 2};
delete foo_init;
// auto zorg = new T();
// delete zorg;
// error: use of deleted function 'std::array<A, 2>::array()'
// note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed:
// error: use of deleted function 'A::A()'
auto zorg_init = new T({3, 4});
delete zorg_init;
}
This code does compile (with no warning) and generates the following output:
A::~A() 0
A::~A() 38870160
A::A(int) 1
A::A(int) 2
A::~A() 2
A::~A() 1
A::A(int) 3
A::A(int) 4
A::~A() 4
A::~A() 3
And now something seems not fine to me. How it that possible that the line auto foo = new T{}; is not considered ill-formed? The initialization of class A is completely bypassed here.
It is worth nothing that this code doesn't compile:
int main() {
auto p = new A{};
delete p;
}
The error being the expected:
error: use of deleted function 'A::A()'
I tested those codes with the following options: -Wall -Wextra -pedantic -std=c++17. gcc -v gives: gcc version 7.2.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) on my computer.
Any explanation will be greatly appreciated :)
PS : after exhaustive tests in Compiler Explorer (using with sample https://godbolt.org/z/5VZLU_), it seems that this is an issue in gcc 7.x. Indeed, only 7.x versions of gcc compile this sample. gcc 6.4 doesn't not. gcc 8.1 neither. clang neither. msvc neither.
Can you confirm that there is no UB here and this is really a compiler bug?
The fact that T b{}; doesn't compile while new T{} makes it evident that this is a compiler bug anyway. However, the standard itself is probably defective. Here's why.
The technical answer to this question is: the code is well-formed, because the standard specifically says so. [array.overview]/2
An array is an aggregate that can be list-initialized with up to N
elements whose types are convertible to T.
The initializer {} indeed contains "up to N elements whose types are convertible to T" — it contains 0 elements, all of which are convertible to A.
How are the elements initialized then? The standard isn't really clear on this. Here's what [array.overview]/3 says:
An array satisfies all of the requirements of a container and of a
reversible container ([container.requirements]), except that a
default constructed array object is not empty and that swap does
not have constant complexity. [...]
(emphasis mine)
The standard does not mention how the elements of this non-empty array are initialized.
Suppose that std::array is implemented like this (for N == 2):
template <class T, std::size_t N>
struct array {
T __elem[N];
};
Then the elements are copy-initialized from {} according to [dcl.init.aggr]/8:
If there are fewer initializer-clauses in the list than there are
elements in a non-union aggregate, then each element not explicitly
initialized is initialized as follows:
(8.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
(8.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
(8.3) Otherwise, the program is ill-formed.
[...]
which is ill-formed according to [dcl.init.list]/3 (the deleted default constructor is selected).
This means that the standard considers the common implementation to be wrong, which is probably not intended.
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.
Could somebody tell me the theory behind this?
Why the last call doesn't compile?
test.cc: In function ‘int main()’: test.cc:15:12: error: too many braces around initializer for ‘int’ [-fpermissive] test.cc:15:12:
error: invalid conversion from ‘’ to ‘int’ [-fpermissive] test.cc:9:6: error: initializing argument 1 of ‘void f(std::initializer_list)’ [-fpermissive] test.cc:15:12:
error: aggregate value used where an integer was expected
I think either c++11 or g++ 4.7 is broken on this.
Thank you!
#include <initializer_list>
class A {
public:
A(const std::initializer_list<int>) {}
};
void f(const std::initializer_list<int>) {}
int main() {
A({1}); // Compile OK
f({1}); // Compile OK
A({{{1}}}); // Compile OK
//f({{{1}}}); // Compile Error.
}
Here is what I believe GCC is thinking.
This is your program with 1 extra line, and the interesting lines numbered.
int main() {
A({1}); // 1. Compile OK
f({1}); // 2. Compile OK
A{{1}}; // 3. Compile OK, equivalent to 1.
A({{{1}}}); // 4. Compile OK
//f({{{1}}}); // 5. Compile Error.
}
Why does GCC compile 4 but not 5?
For clarity, suppose the construction at #4 actually declared something:
A a{{{1}}}; // 4a. Compile OK
GCC asks if the argument of the constructor, which is {{1}} is
implicitly convertible to A. So is:
A{{1}}
a valid conversion from {{1}} to A? Yes it is - as per 3.
This reasoning, of course, is not applicable to #5; hence error.
If you want to stop GCC from accepting #4, then block the
enabling conversion by making the enabling constructor explicit:
class A {
public:
explicit A(const std::initializer_list<int> il) {}
};
Then #4 will give the error:
error: converting to ‘A’ from initializer list would use explicit constructor ‘A::A(std::initializer_list<int>)’
A {1} can initialize an int. A {{1}} probably should not - there is a defect report on tbe committee for that. GCC forbids that and clang just tends to emit warnings about redundant braces currently.
When X is a class that has copy or move constructors, then X ({...}) may invoke invoke one of them. Notice that X {...} may too, but is restricted to not allow user defined conversions (for a copy or move constructor).
Now with your A ({{{1}}}) the first brace is consumed by the copy/move constructor. The second goes to the initializer list recursively. And the third goes to the contained int.
According to the Standard, adding one more brace will break for A ({{{{1}}}}). Because the second brace would need to be consumed by a copy/move constructor of A but need a user defined conversion sequence. The same holds for A {{{{1}}}}, which is invalid for this reason too.