Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[class.copy.ctor] p15 Only the corresponding active object can be created and copied from the source #5193

Open
xmh0511 opened this issue Jan 10, 2022 · 22 comments

Comments

@xmh0511
Copy link
Contributor

xmh0511 commented Jan 10, 2022

[class.copy.ctor] p15 says

The implicitly-defined copy/move constructor for a union X copies the object representation ([basic.types]) of X. For each object nested within ([intro.object]) the object that is the source of the copy, a corresponding object o nested within the destination is identified (if the object is a subobject) or created (otherwise), and the lifetime of o begins before the copy is performed.

However, [class.union.general] p2 says

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]). At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

It doesn't make sense that we identify or create the corresponding object o for each object nested within the object. Since [basic.life] p1 says such an initialization will begin the lifetime of that object. If we copy each object into the destination, it will make no sense. Consider this example

union U{
  int i;
  char c;
};
U u;
u.i = 0;  // begin the lifetime of the object corresponding to NSDM `U::i`, such that is active
U u2 = u;  // #1

I think the copy initialization at #1 just needs to create the object that corresponds to U::i into u2 and copy the data from u.i. So, is the following phrase the intent of [class.copy.ctor] p15?

The implicitly-defined copy/move constructor for a union X copies the object representation ([basic.types]) of X. For the object to which the active non-static data member corresponds in the source of the copy, a corresponding object o nested within the destination is identified (if the object is a subobject) or created (otherwise), and the lifetime of o begins before the copy is performed.

The similar issue is also in [class.copy.assign]p13.

@languagelawyer
Copy link
Contributor

languagelawyer commented Jan 10, 2022

It doesn't make sense that we identify or create the corresponding object o for each object nested within the object. Since [basic.life] p1 says such an initialization will begin the lifetime of that object.

[basic.life]/1:

except that if the object is a union member subobject or subobject thereof nested within it


So, is the following phrase the intent of [class.copy.ctor] p15?

The intent is more like

For each object oS nested within ([intro.object]) the object that is the source of the copy, a corresponding object oD nested within the destination is identified (if the object oS is a subobject) or created (otherwise), and, if oS is within its lifetime, the lifetime of oD begins before the copy is performed.

except that we may not want to start the lifetime of objects of a non-implicit-lifetime type (and everything nested within them), e.g. a vector stored in a union member subobject:

union U { unsigned char buf[9000]; } u {};
auto* pv = new (&u.buf) std::vector<int> {};

U u2 = u; // do we want to start the lifetime of the object corresponding to *pv? Maybe not even create it?

@zygoloid

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

@languagelawyer Seems right, I think the last sentence is more accurate if it is that

the lifetime of oD begins before the copy from Os is performed.

Consider this example

union U{
  char c = 0;
  int a;
} u;

There are two objects nested within the object u, how will the copy perform? Firstly copy c, then copy a? Or, we just copy c since it is active?

@languagelawyer
Copy link
Contributor

There are two objects nested within the object u, how will the copy perform? Firstly copy c, then copy a?

There is no first or second, the whole object representation of u is copied:

The implicitly-defined copy/move constructor for a union X copies the object representation ([basic.types]) of X.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

@languagelawyer if oS is a subobject, Does this subobject intend to refer to indirect subobject?

union U{
  struct C{
    int i;
    struct X{} x;
  } c = {0};
  char c2;
};
U u1;
U u2 = u1;

U::c::i and U::c::x are all subobjects of U::c, which in turn is a subobject of u1, they all within their lifetime. So, U::c::i, U::c::x and U::c in u2 will be identified and their lifetime will begin before the copy.

@languagelawyer
Copy link
Contributor

Does this subobject intend to refer to indirect subobject?

There is no such thing as direct or indirect subobject. NSDM is what can be direct or indirect.
Subobject is just subobject. It means an object which corresponds to a NSDM, a base class, or which is an array element.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

Does this subobject intend to refer to indirect subobject?

There is no such thing as direct or indirect subobject. NSDM is what can be direct or indirect. Subobject is just subobject. It means an object which corresponds to a NSDM, a base class, or which is an array element.

I meant the subobjects corresponding to indirect NSDMs or Base classes. Do these subobject conform to be os?

@languagelawyer
Copy link
Contributor

I meant the subobjects corresponding to indirect NSDMs or Base classes. Do these subobject conform to be os?

Yes, why not.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

@languagelawyer Alright, I think your proposal looks good to me.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

@languagelawyer Hmmm... I still think we should completely say what these subobjects os can be. Since, for a non-union class, the members of the base classes are also members of the derived class, hence, we can say the subobject of these members are subobjects of the object of the derived class, as per [intro.object] p2

A subobject can be a member subobject ([class.mem]), a base class subobject ([class.derived]), or an array element.

However, this is different in a union class. Firstly, a union class cannot have any base class. There is also no provision that says that: the subobject of an object corresponding to the NSDM of a union is also a subobject of that union.

union U{
   struct X{ int a;} x;
} u;

x is a subobject of u, X::a is a subobject of x, however, it never says that X::a is a subobject of u. So, should we change if oS is a subobject to if oS is a subobject or a subobject thereof?

@languagelawyer
Copy link
Contributor

Since, for a non-union class, the members of the base classes are also members of the derived class

It means members, not member subobjects.

hence, we can say the subobject of these members are subobjects of the object of the derived class, as per [intro.object] p2

I don't see the connection between the first and the second part.

There is no provision that says that the subobject of an object corresponding to the NSDM of a union is also a subobject of that union.

And we don't want such provision. There is «nested within» relation for such transitivity.

So, should we change if oS is a subobject to if oS is a subobject or a subobject thereof?

We don't care whose subobject oS is, only is that it is a subobject.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2022

For the first opinion, since a subobject corresponding to a member is a member subobject, hence any subobject corresponding to a direct/indirect member is also a member subobject of the containing object.


Do we indeed not care whether the object os is a subobject of the source object in the copy? Consider this example

struct C{
   int a = 0;
};
union U{
  char buff[128];
};
U u = {0};   // start the lifetime of buff
new (u.buff + 4) C;  

U u2 = u;

Although, the created object by the new-expression is not a subobject of buff or u, however, the object C::a is a subobject of the object of type C. Is C::a the os here? It is similar to the example in your first comment. Presumably, C::a is not os. Maybe, we just want the subobject of u or the subobject ... of one subobject of u to be os?

@languagelawyer
Copy link
Contributor

languagelawyer commented Jan 10, 2022

For the first opinion, since a subobject corresponding to a member is a member subobject, hence any subobject corresponding to a direct/indirect member is also a member subobject of the containing object.

We want the «nested within» relation to be a tree, not a DAG, so an object corresponding to a base class (direct) NSDM is not a subobject of an object of derived type. (However, see #2310)


Do we indeed not care whether the object os is a subobject of the source object in the copy?

We do not care.

Is C::a the os here?

Check the definition of https://timsong-cpp.github.io/cppwp/n4868/intro.object#def:nested_within

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 11, 2022

@languagelawyer I think I have seen your point. Did you mean

For each object oS nested within ([intro.object]) the object O that is the source of the copy, a corresponding object oD nested within the destination is:

  • if the object oS is a subobject of O, identified, or
  • if the object oS is nested within O but not a subobject of O, created

in either case, if oS is within its lifetime, the lifetime of oD begins before the copy is performed.


This may introduce an issue that if Os has a non-trivially copyable type. If we say we begin the lifetime of oD, that means we can manipulate oD in any way as per [basic.life] after copying from oS?

@languagelawyer
Copy link
Contributor

Did you mean … For each object oS nested within ([intro.object]) the object O that is the source of the copy … if the object oS is a subobject of O … if the object oS is nested within O but not a subobject of O

I think I wrote this several times: oS is just a subobject. Without "of".

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 11, 2022

@languagelawyer Sorry. Maybe, I still don't understand your point.

struct Vector{
   int buff[2];
};
union U{
  struct C{
    char buff[1000];
    int i;
    struct X{} x;
  } c = {0};
  char c2;
};
U u;
auto ptr = new(u.c.buff +4) Vector{};

I wonder which objects in u can be called oS?

@languagelawyer
Copy link
Contributor

I wonder which objects in u can be called oS?

Every object which is nested within u

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 11, 2022

I wonder which objects in u can be called oS?

Every object which is nested within u

@languagelawyer So, oS can be every object nested within the u, Right? Then, in a destination, which objects will be identified? which objects will be created? Moreover, which objects will be begun their lifetime in the destination?

@languagelawyer
Copy link
Contributor

Then, in a destination, which objects will be identified? which objects will be created? Moreover, which objects will be begun their lifetime in the destination?

See #5193 (comment)

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 12, 2022

See #5193 (comment).

So, the conclusion to the above example in that comment is that:

Subobjects are:

  • the subobjects of u
  • u.c2
  • u.c
  • the subobjects of u.c
  • u.c. buff
  • u.c.i
  • u.c.x
  • the subobjects of each element of u.c. buff

  • the subobjects of the object referred to by *ptr

  • (*ptr). buff.
  • the subobjects of each element of (*ptr).buff

which are all so-called subobjects nested within u and are identified

Instead, the object that is created is:

  • the object referred to by *ptr

which is nested in u but itself is not a subobject.

Is this also your opinion?

@frederick-vs-ja
Copy link
Contributor

except that we may not want to start the lifetime of objects of a non-implicit-lifetime type (and everything nested within them), e.g. a vector stored in a union member subobject:

Perhaps we also need to allow other types for implicitly-defined assignment operators.
Consider the following example:

struct Weird {
    // no default ctor
    Weird(int) {}
    Weird(const Weird&) {} // non-trivial
    Weird(Weird&&) {} // non-trivial
    Weird& operator=(const Weird&) = default;
    Weird& operator=(Weird&&) = default;
};

union Foo { // has deleted default/copy/move ctor, and eligible trivial copy/move assignment
    char dummy;
    Weird w;
};

int main()
{
    Foo x{.w{42}}, y{.dummy{}};
    y = x; // it seems that y.w should be made active
}

In this case, Weird is neither implicit-lifetime nor trivially copyable, but it makes sense that y = x makes y.w active.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 12, 2022

@frederick-vs-ja Sure, I have said that

The similar issue is also in [class.copy.assign]p13.

So, [class.copy.assign]p13 is reasonable to be changed if [class.copy.ctor] p15 has revisioned.

@languagelawyer
Copy link
Contributor

it makes sense that y = x makes y.w active.

When y.w = x.w doesn't? I'm not sure.

Anyway, this union shenanigans need a holistic review to decide how it interact with each other and the rest of the object model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants