I want to create two objects with mutual member-references between them. Later it can be extended to e.g. closed loop of N referencing objects, where N is known in compile time.
The initial attempt was with the simplest struct A lacking any constructors, which make it an aggregate (v simulates some payload):
struct A {
const A & a;
int v = 0;
};
struct B {
A a1, a2;
};
consteval bool f()
{
B z{ z.a2, z.a1 };
return &z.a1 == &z.a2.a;
}
static_assert( f() );
Unfortunately it is not accepted by the compilers due to the error:
accessing uninitialized member 'B::a2'
which is actually strange, because no real read access is done, only remembering of its address. Demo: https://gcc.godbolt.org/z/cGzYx1Pea
The problem is solved after adding constructors in A, making it not-aggregate any more:
struct A {
constexpr A(const A & a_) : a(a_) {}
constexpr A(const A & a_, int v_) : a(a_), v(v_) {}
const A & a;
int v = 0;
};
Now all compilers accept the program, demo: https://gcc.godbolt.org/z/bs17xfxEs
It is surprising that seemingly equivalent modification of the program makes it valid. Is it really some wording in the standard preventing the usage of aggregates in this case? And what exactly makes the second version safe and accepted?
B z{ z.a2, z.a1 }; attempts to copy-construct a1 and a2, rather than aggregate-initialize them with z.a2, z.a1 as first fields.1
B z{{z.a2, 0}, {z.a1, 0}}; works in GCC and Clang. MSVC gives error C2078: too many initializers, which looks like a bug.
1 Here, direct-list-initialization is performed for z, which in this case resolves to aggregate initialization, which in turn performs copy-initialization for each member, and:
[dcl.init.general]/15.6.2
... if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered.
So, because initializers z.a2, z.a1 have the same type as the corresponding members, the aggregate-ness of the members is ingored, and copy constructors are used.
Related
I know it's a subject that has been discussed quite extensively here on stackoverflow but I've a hard time finding thorough answers removing all the confusion I've concerning list initialization and initializer_lists in C++, so I will just give it a try and ask my own questions.
Please, consider the following snippet of code :
class C
{
public :
C(int a, int b, int c) : _a (a), _b(b), _c(c) {}; //initialization_list with ()
//C(int a, int b, int c) : _a{ a }, _b{ b }, _c{ c } {}; //initialization list with {}
private :
int _a, _b, _c;
};
int main()
{
C a(5.3,3.3,4.3); // no list
C b{5.3,3.3,4.3}; // list {}
C c({5.3,3.3,4.3}); // list {}
}
I don't understand why those two initialization lists behave similarly? I was expecting, when trying to create the object a of type C using the initialization list of type _a{a}, _b{b}, _c{c} to get a compiler error about narrowing. However, no errors are generated and _a, _b and _c just store the integer values.
It's only when creating the objects b or c using a list "{}" that the compiler generates a narrowing error message. Why is that? Is there any differences between writing an initialization list using {} or () that I'm unaware of or are the behaviours identical?
Come my next question:
class C
{
public :
//private :
int _a, _b, _c;
};
int main()
{
C a(5,3,4); //obviously doesn't work as no such constructor
C b{5,3,4}; //work only if _a, _b and _c are not private nor protected!
}
How comes that the second statement (with braces) only works if the variables are public? What is the mechanism involved?
So I would like to better understand, beside the "narrowing safety" provided by creating an object with a list {}, what other "functionalities" does this list mechanism provide ? Because in the second call, it's still the default constructor that is called (hence, not a default constructor taking an initializer_list as argument), right ?
Lastly, imagine in my class C I've another constructor taking an initializer list as parameter.
class C
{
public :
C() = default; //default constructor
C(int a, int b, int c) : _a (a), _b(b), _c(c) {};
C(std::initializer_list<int> a) { //do some stuffs with the list};
private :
int _a, _b, _c;
};
It's pretty obvious that, if trying to create an object taking any numbers of integers but 3 (or 0 actually), the constructor taking the initializer_list will be invoked. If creating an object like that however :
C c();
or
C c{};
the default constructor will be called. But if creating an object with exactly 3 integers :
C c(5,2,3);
or
C c{5,2,3};
the initializer_list constructor will be called. The rule goes like that :
If either a default constructor or an initializer-list constructor could be invoked, prefer the default constructor
If both an initializer-list contructor and an "ordinary constructor" could be invoked, prefer the initializer-list constructor
Therefore (and correct me if I'm wrong), if I create my object like that :
C c{5,3,4};
The iniatializer-list constructor will be called. However, if I create my object like that :
C c(5,3,4);
The second constructor (taking 3 ints as arguments) will be called. My question is : how can I create an object with this second constructor instead of the iniatializer-list one if I want to provide narrowing safety as well ? (because if I do as in the first example of this question, the initializer-list constructor will be called instead !).
Don't hesitate to examplify your replies and to discuss on list-related concepts I haven't talk in this question. I would like to get a very good grasp on those. Thanks.
So anytime curly braces are used you are using aggregate initialization, a method of initialization for structs or classes that initializes in order., or via a designator. For example,
#include <iostream>
struct Foo
{
int a;
char b;
};
class Doo
{
public:
double h;
char ch;
};
int main() {
Foo first = {3, 't'};
std::cout << first.b << "\n";
//t
Doo second = {'3', 50};
std::cout << second.ch << "\n";
//2 interpreted as char
}
Here, when we use the {} to initialize a class or struct, they are always interpreted as being in the order listed in the class. That's why '2' was printed since 50 in ASCII corresponds to the character '2'.
Constructor Initialization
So you can also use the same logic with constructor initialization lists,
#include <iostream>
struct Pair
{
int first;
long second;
};
class Couple
{
public:
Pair p;
int g;
public:
Couple(): p{3, 700}, g{3}
{}
};
int main() {
Couple test;
std::cout << test.p.first << "\n";
//3
}
Here, the {3, 700} next to p, would be the same as Pair p = {3, 700}; used else where in code. Your basically using an in order aggregate initialization. Now, what happens if we change the curly braces for the Pair field to parenthesis?
We get this error
main.cpp: In constructor 'Couple::Couple()':
main.cpp:15:26: error: no matching function for call to 'Pair::Pair(int, int)'
Couple(): p(3, 700), g{3}
That's because we don't have a constructor for Pair that accepts two numbers. So the key difference between the aggregate initialization and the parenthesis is you need to have constructors implemented for any specific set of arguments you make with parenthesis, yet with curly braces you can just use the default one the compiler hands you.
The std::initializer_list is a not-commonly used form of container for multiple arguments in initialization lists with {}.
I would have initially expected the following code to compile:
#include <set>
using namespace std;
class A {
public:
set<int> st;
A(set<int> s) : st(s) {}
};
int main() {
A a = {1,2,3}; // Error, couldn't convert from initializer list
A b({1,2,3}); // Ok
}
I can't understand why the first construction fails but the second one succeeds. I tried to replicate the error without the use of initializer list but I couldn't do so.
I am using vc12, but it also fails on the microsoft online visual compiler (with a different error message and code to my compiler).
Should this compile?
If not why not?
EDIT:
The following does compile (EDIT: Only in visual studio and this is a bug.):
class A {
public:
int i;
A(int j) : i(i) {}
};
class B {
public:
A a;
B(A o) : a(o) {}
};
class C {
public:
B b;
C(B u) : b(u) {}
};
int main() {
C c = A(10); // Compiles just fine, but isn't this doing the same set of implicit conversions?
C v(A(10));
}
The two seem the same to me in terms of the number of implicit conversions.
Given this code:
#include <set>
using namespace std;
class A {
public:
set<int> st;
A(set<int> s) : st(s) {}
};
int main() {
A a = {1,2,3}; // Error, couldn't convert from initializer list
A b({1,2,3}); // Ok
}
The declaration of a, using the “=” syntax, is a copy initialization with a brace-enclosed list.
If A were a Plain Old Data type or an “aggregate” then the values in the list would be used to initialize the items of a in order, and the rest of the items, if any, would be zero-initialized.
If A instead had a constructor taking a suitable std::initializer_list argument, then that would have been used.
Those two possibilities are exhaustive. For example, if the constructor with std::set had been considered (it isn't considered), then that would have involved two implicit user-defined conversions to produce the temporary object on the right hand side of =, namely std:initializer_list → std::set, and then std:.set → A. And the rules of C++ limit an implicit conversion sequence to at most one user defined conversion.
And so, since class A is neither POD/aggregate nor a class with std::initializer_list construction, you get an error.
The declaration of b is direct initialization. Here the provided argument is used not to produce a temporary A, but to produce an argument acceptable to some A constructor. One such possibility is found: using the argument to produce a std::set.
When initializing an object with = the compiler implicitly creates a temporary object of your type and then copies it to your variable (although the temporary and copy may, and usually are, elided).
So your a object is theoretically equivalent to:
A a = A(std::set<int>({1, 2, 3}));
I'm not sure exactly why the behaviour differs but I think it's due to the fact that the compiler is allowed to only perform one user defined conversion implicitly. In this case it's probably considered two separate UDCs:
initializer list to std::set
std::set to A
In first case you are trying to construct your object using constructor which takes an std::initializer_list as single parameter (which is not implemented). In your second call a temporary std::set<int> instance is constructed and your A(set<int> s) constructor is called.
In order to fix it implement the missing constructor:
A(std::initializer_list<int> list) : st(list) { }
Edit: Just to be clear, the struct doesn't do anything, as in it has no functions. I think I gave the impression that I thought using an initialiser list and leaving the body of the constructor empty was the issue at hand.
Say I'm using a struct to hold two values, and I have a constructor just so I can create an arbitrary struct like this:
struct twoValues
{
int x;
int y;
twoValues(int x_, int y_):y(y_),x(x_)
{}
};
someFunction(twoValues(1,2));
That saves me from having to do this:
twoValues anInstance;
anInstance.x=1;
anInstance.y=2;
someFunction(anInstance);
Edit: You're all correct, I could also initialise with the following:
twoValues anInstance = {1,2};
I see nothing wrong with this but I had some feedback from a C++ test and one of the negative feedback marks was "constructors for structs that don't do anything". I had limited contact with the guy testing me and so never asked why.
Is it a bad thing and why? I would rather carry on doing it.
It depends on what the struct is being used for. As others have said,
the constructor means that the class is no longer a POD, and that
aggregate initialization cannot be used for it. In particular, you
cannot have something at namespace scope like:
TwoValues const table[] =
{
{ 1, 2 },
{ 3, 4 },
// ...
};
You can have:
TwoValues const table[] =
{
TwoValues( 1, 2 ),
TwoValues( 3, 4 ),
// ...
};
but it is more verbose, and it implies dynamic initialization, which may
result in order of initialization issues.
On the other hand, without the constructor, you cannot create temporary
instances on the fly. Instead of:
extern void f( TwoValues const& );
// ...
f( TwoValues( 1, 2 ) );
you have to write:
extern void f( TwoValues const& );
// ...
TwoValues tmp = { 1, 2 };
f( tmp );
If the object is dynamically allocated, it's even worse, since you
either have to allocate first, then initialize, or create a temporary as
above, and then write new TwoValues( tmp ) and use the implicit copy
constructor.
You have to choose. Depending on what the struct is used for, one or
the other will be preferred; on one hand, I have a lot of structs which
are used exclusively in static tables, and intentionally don't have a
constructor (and contain only types which support static
initialization), and use them a lot for configuring code. On the other
hand, I also have a lot of structs which are internal to a class, along
the lines of Node in a tree or a graph; these almost always have a
constructor, to facilitate creating them on the fly. There's no
"correct" answer without knowing the role of the struct in your
application.
Declaring an empty constructor has side-effects..
Even though your constructor has an empty body it is still considered to be a constructor, and therefore certain object properties will be lost - such as the object being POD.
Some cases require the use of Plain Old Data-types, which can make it undesirable to do what you've done without a specific reason.
Read more about initialization without a defult constructor in the next section.
c++03 && c++11
You can initialize your members with values without explicitly defining a "constructor that doesn't do anything", just use the brace-initialization syntax as in the below snippet.
struct Obj {
int x, y;
};
Obj a = {1,3}; /* a.x = 1, a.y = 2 */
c++03
The downside of not having a constructor is that you cannot initialize the object using = { ... } in certain circumstances when writing C++03.
C++11 fixes this for you, see the next section for relevant examples.
c++11
In C++11 the initialization using braces ( = { ... }) has been given increased functionality.
As seen in the below snippet where the defined constructor of Obj is called even though we use the same form of initialization as earlier in this post.
struct DoubleBoth {
DoubleBoth (int x, int y)
: x(x*2), y(y*2)
{}
int x, y;
};
The snippets below were all illegal prior to C++11:
DoubleBoth a = {1,2}; /* a.x = 2, a.y = 4 */
struct Wrapper {
Wrapper ()
: value {3,4}
{}
DoubleBoth value;
};
void func (DoubleBoth v = {1,2}) { // c++11 only
...
}
func ({4,5}); // c++11 only, c++03 requires `DoubleBoth obj (0,1); func (obj);`
It's much safer to have this kind of constructor, than to initialize with a list.
When you initialize with a list :
twoValues anInstance = {1, 2};
It really depends on the order of the members in the struct. Which in most cases is totaly random to begin with.
If another programmer happens to add another member, or to sort the members by alphabet, it wont work correctly.
Having a constructor that assigns the values to the right member by NAME is much safer.
You've stopped twoValues from being a POD and have prevented instances of the struct from being default- or value- initialized which are often desirable properties. Personally, I would prefer to have a free function for making temporary instances if you need a simple C++03 friendly approach.
E.g.
twoValues makeTwoValues(int x_, int y_)
{
twoValues tmp = { x_, y_ };
return tmp;
}
void f() {
someFunction(makeTwoValues(1,2));
}
E.g. initializing a member of type twoValues
class X {
twoValues tv;
public:
X(int x, int y) : tv(makeTwoValues(x, y)) {}
};
Probably because you can initialize it like this:
twoValues anInstance = {1, 2};
However, without the constructor you cannot initialize a anInstance in another struct's initializer list in C++03. For example:
struct Bar {
twoValues v_;
Bar() : v_(1,2) {} // error!
Bar() { v_ = {1,2}; } // have to assign value in constructor body
Bar() : v_{1,2} {} // OK in C++11
};
so in fact the constructor does something, and it does serve a very useful purpose in C++03. It is less of an issue in C++11.
There is nothing wrong with this contructor.
Also, it does something, it affect values to x and y, which is what I would expect from such a constructor.
But, maybe there are some coding rules in your company that says that you should do it another way. In that case I would recommend you ask the person that gave you the feedback and make the necessary modifications.
The constructor makes the struct a non-POD which may not be desirable in some cases.
If your struct doesn't have constructor, then your struct will be POD1, and you will be allowed to initialize like this:
twoValues anInstance = {1,2}; //C++03 and C++11 both!
twoValues anInstance {1,2}; //C++11 only
which are good.
1. Actually there are other things which make a struct non-POD. So just because your struct doesn't have constructor doesn't necessarily mean it is POD. You would like to see this post to know what those other things are.
Structures can have constructors, and the syntax is the same as for classes in C++. The difference between a class and a struct is that class-members are private by default, while struct-members default to public, however Unions will not allow constructors in the structs. You can make a constructor on the union though.
Consider the following pair of mutually referencing types:
struct A;
struct B { A& a; };
struct A { B& b; };
This can be initialized with aggregate initialization in GCC, Clang, Intel, MSVC, but not SunPro which insists that user-defined ctors are required.
struct {A first; B second;} pair = {pair.second, pair.first};
Is this initialization legal?
slightly more elaborate demo: http://ideone.com/P4XFw
Now, heeding Sun's warning, what about classes with user-defined constructors? The following works in GCC, clang, Intel, SunPro, and MSVC, but is it legal?
struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };
struct {B first; A second;} pair = {pair.second, pair.first};
demo: http://ideone.com/QQEpA
And finally, what if the container is not trivial either, e.g. (works in G++, Intel, Clang (with warnings), but not MSVC ("pair" unknown in initializer) or SunPro ("pair is not a structure")
std::pair<A, B> pair(pair.second, pair.first);
From what I can see, §3.8[basic.life]/6 forbids access to a non-static data member before lifetime begins, but is lvalue evaluation of pair.second "access" to second? If it is, then are all three initializations illegal? Also, §8.3.2[dcl.ref]/5 says "reference shall be initialized to refer to a valid object" which probably makes all three illegal as well, but perhaps I'm missing something and the compilers accept this for a reason.
PS: I realize these classes are not practical in any way, hence the language-lawyer tag. Related and marginally more practical old discussion here: Circular reference in C++ without pointers
This one was warping my mind at first but I think I got it now. As per 12.6.2.5 of 1998 Standard, C++ guarantees that data members are initialized in the order they are declared in the class, and that the constructor body is executed after all members have been initialized. This means that the expression
struct A;
struct B { A& a; };
struct A { B& b; };
struct {A first; B second;} pair = {pair.second, pair.first};
makes sense since pair is an auto (local, stack) variable, so its relative address and address of members are known to the compiler, AND there are no constructors for first and second.
Why the two conditions mean the code above makes sense: when first, of type A, is constructed (before any other data member of pair), first's data member b is set to reference pair.second, the address of which is known to the compiler because it is a stack variable (space already exists for it in the program, AFAIU). Note that pair.second as an object, ie memory segment, has not been initialized (contains garbage), but that doesn't change the fact that the address of that garbage is known at compile time and can be used to set references. Since A has no constructor, it can't attempt to do anything with b, so behavior is well defined. Once first has been initialized, it is the turn of second, and same: its data member a references pair.first, which is of type A, and pair.first address is known by compiler.
If the addresses were not known by compiler (say because using heap memory via new operator), there should be compile error, or if not, undefined behavior. Though judicious use of the placement new operator might allow it to work, since then again the addresses of both first and second could be known by the time first is initialized.
Now for the variation:
struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };
struct {B first; A second;} pair = {pair.second, pair.first};
The only difference from first code example is that B constructor is explicitly defined, but the assembly code is surely identical as there is no code in the constructor bodies. So if first code sample works, the second should too.
HOWEVER, if there is code in the constructor body of B, which is getting a reference to something (pair.second) that hasn't been initialized yet (but for which address is defined and known), and that code uses a, well clearly you're looking for trouble. If you're lucky you'll get a crash, but writing to a will probably fail silently as the values get later overwritten when A constructor is eventually called. of
From compiler point of view references are nothing else but const pointers. Rewrite your example with pointers and it becomes clear how and why it works:
struct A;
struct B { A* a; };
struct A { B* b; };
struct {A first; B second;} pair = {&(pair.second), &(pair.first)}; //parentheses for clarity
As Schollii wrote: memory is allocated beforehand, thus addressable. There is no access nor evaluation because of references/pointers. That's merely taking addresses of "second" and "first", simple pointer arithmetics.
I could rant about how using references in any place other than operator is language abuse, but I think this example highlights the issue well enough :)
(From now on I write all the ctors manually. Your compiler may or may not do this automagically for you.)
Try using new:
struct A;
struct B { A& a; B(A& arg):a(arg){;} };
struct A { B& b; A(B& arg):b(arg){;} };
typedef struct PAIR{A first; B second; PAIR(B& argB, A& argA):first(argB),second(argA){;}} *PPAIR, *const CPPAIR;
PPAIR pPair = NULL;// just to clean garbage or 0xCDCD
pPair = new PAIR(pPair->second, pPair->first);
Now it depends on order of execution. If assignment is made last (after ctor) the second.p will point to 0x0000 and first.ref to e.g. 0x0004.
Actually, http://codepad.org/yp911ug6 here it's the ctors which are run last (makes most sense!), therefore everything works (even though it appears it shouldn't).
Can't speak about templates, though.
But your question was "Is that legal?". No law forbids it.
Will it work? Well, I don't trust compiler makers enough to make any statements about that.
I have a custom class that I want to behave like a built-in type.
However I have noticed that you can initialise a const variable of that class without providing an initial value. My class currently has an empty default constructor.
Here is a comparison of int and my class foo:
int a; // Valid
int a = 1; // Valid
const int a = 1; // Valid
const int a; // Error
foo a; // Valid
foo a = 1; // Valid
const foo a = 1; // Valid
const foo a; // Should cause an error, but it compiles
As you can see I need to prevent
const foo a;
from compiling.
Any ideas from C++ gurus?
It compiles only if it has a default constructor, and it compiles because it has it, which means that it is initialized. If you don't want that line to compile, just disable the default constructor (will also make foo a; an error as an unwanted side effect). Without a definition of foo or what you want to do, this is as far as I can get.
I don't think there is any way of achieving what you want (i.e. allow the non-const variable to be default initialized, while having the const version fail compilation and allowing the other use cases --that require providing constructors)
The rules of C++ simply say that default-initialization (e.g. new T;) and value-initialization (e.g. new T();) are the same for objects of class type, but not for objects of fundamental type.
There's nothing you can do to "override" this distinction. It's a fundamental part of the grammar. If your class is value-initializable, then it is also default-initializable.
There is a sort-of exception for classes without any user-defined constructors: In that case, initialization of members is done recursively (so if you default-init the object, it tries to default-init all members), and this will fail if any of the class members are themselves fundamental, or again of this nature.
For example, consider the following two classes:
struct Foo { int a; int b; };
struct Goo { int a; int b; Goo(){} };
//const Foo x; // error
const Goo y; // OK
The implicit constructor for Foo is rejected because it doesn't initialize the fundamental members. However, y is happily default-initialized, and y.a and y.b are now "intentionally left blank".
But unless your class doesn't have any user-defined constructors, this information won't help you. You cannot "forward" the initialization type to a member (like Foo() : INIT_SAME_AS_SELF(a), b() { }).