On Sunday, August 30, 2015 at 10:05:49 AM UTC-4, Klaim - Joël Lamotte wrote:I think your inline class idea better have it's own discussion thread so that exploring the concept can be focused on it.
The thing it most needs is justification is why we need such a feature. Can it do something more than give us property hack fixes?
A more useful thing to explore is the concept of empty member optimizations (which would be necessary to make properties worthwhile with these). It wouldn't be acceptable to have the standard suddenly break lots of code by declaring that all empty classes that are members of other classes no longer take up space.
But would it be possible to decorate a class (using contextual keywords ala `final`) such that the class would be considered to be truly empty. And that such truly empty classes would not take up space in structs. If that is possible, it could have a multitude of uses.
Another point: it seems to me that the definition of such construct could be reused inside the same class or inside several classes.So finding a way to avoid having to redefine it all the time would be more interesting, I think.
That doesn't make sense, at least not for building properties out of them. With properties, you need to access variables from the owning class. And there's no way to do that with template metaprogramming. So each property-via-inline-class would have to be unique, coded for a specific variable or variables.
Nicol, I think you misunderstand the concept of inline classes as discussed here, thought wasn't very well explained to be honest.
Inline classes (like nested classes in Java) can only exist as part of their parent class and can access all their members and methods. Because they can only exist as part of their containing class there is no need for a this pointer, or storage if they have no members. The compiler can convert all access to the inline class to access to the parent object. Maybe translated to C++ the concept is more like a class-local namespace, but not quite. It cannot be copied out of a class because it can only exist as part of its containing object.
Whether calling it "class" or something else, is really just bikeshedding. One should first determine its conceptual and technical properties before starting the naming debate. I don't claim to have all the answers when it comes to applying this concept to C++ and I don't claim there aren't C++ specificy problems that need solving. It just felt like the concept wasn't explained very much and therfore caused ambiguity.
Here some hypothetical example code:
class A {
int x_data;
public:
inline struct x {
x& operator=(int value) {
x_data = value; // Can access members of containing class
return *this; // "this" can be impl converted A* <-> A::x*
}
int operator int() const { return x_data; }
};
};
A a;
a.x = 5; // Converted to a.x_operator=(5)
cout << a.x; // Converted to a.x_operator_int()
Even though the setter returns A::x&, the inline class A::x can only ever exist as reference/pointer outside of A and is only copyable as part of its containing object, therefore it is not a proxy. To the compiler A* and A::x* are the same value, they only change what operations are allowed on the target. In this example A::x has no data members therefore it conceptually doesn't require storage and &a.x == &b.x is only ever true if &a == &b.
A different example where the inline class does have members:
class B {
public:
inline struct x {
x& operator=(int value) { x_data = value; return *this; }
int operator int() const { return x_data; }
private:
int data;
};
int foo() {
return x.data * 2;
}
};
B b;
b.x = 5;
cout << b.foo();
Besides a way to implement properties inline classes can serve to group membes/methods the same way a non-inline nested class currently does except it drops the requirement for a this member pointing to the parent. Even if there was a dedicated property keyword/whatever the implementation technique could be inline classes and thefore kept open for advanced users that want more than just the default getter/setter implementation.
I hope this adequately explains what is meant by inline classes. It just seemed like it wasn't described very well and people were talking past each other.
On 30 August 2015 at 18:49, Nicol Bolas <jmck...@gmail.com> wrote:On Sunday, August 30, 2015 at 10:05:49 AM UTC-4, Klaim - Joël Lamotte wrote:I think your inline class idea better have it's own discussion thread so that exploring the concept can be focused on it.
The thing it most needs is justification is why we need such a feature. Can it do something more than give us property hack fixes?My suggestion was to discuss this outside the properties thread because it's impossible to follow with the several sub-discussions ongoing.
Also, the "justification", however weak, have already been given in the discussion.
A more useful thing to explore is the concept of empty member optimizations (which would be necessary to make properties worthwhile with these). It wouldn't be acceptable to have the standard suddenly break lots of code by declaring that all empty classes that are members of other classes no longer take up space.But would it be possible to decorate a class (using contextual keywords ala `final`) such that the class would be considered to be truly empty. And that such truly empty classes would not take up space in structs. If that is possible, it could have a multitude of uses.I fail to see the relationship with the initial subject or with the proposed inline class.
On Sunday, August 30, 2015 at 3:13:11 PM UTC-4, Klaim - Joël Lamotte wrote:On 30 August 2015 at 18:49, Nicol Bolas <jmck...@gmail.com> wrote:On Sunday, August 30, 2015 at 10:05:49 AM UTC-4, Klaim - Joël Lamotte wrote:I think your inline class idea better have it's own discussion thread so that exploring the concept can be focused on it.
The thing it most needs is justification is why we need such a feature. Can it do something more than give us property hack fixes?My suggestion was to discuss this outside the properties thread because it's impossible to follow with the several sub-discussions ongoing.
You haven't exactly made that easier. You didn't bother to explain what inline classes are, or even link to the initial post talking about it. So if someone isn't willing to comb through the properties thread, they're not going to know what you're talking about.
Also, the "justification", however weak, have already been given in the discussion.
Different threads are different; that's the point of starting them. So if you want to start a discussion purely about inline classes, then you need to explain what they are and justify why they're an important feature.
Just like any proposal thread.
Am 31.08.2015 um 01:05 schrieb Nicol Bolas:
>
> One of the principle failings of all properties-through-objects
> classes in C++ is that the property is an object and must obey the
> rules of C++ objects. Like taking up space in the class that contains
> them.
Why?
You are begging the question here. *You* place this requirement on
these kinds of properties and *you* then conclude that the presented
solutions are impossible because they don't satisfy the requirements you
demand in the *current* language rules.
Well, duh, a new language
feature obviously doesn't work with the current rules because otherwise
it wouldn't be a *new* feature to be added in a *future* version of the
language. The necessary rules can be adapted accordingly.
>
> Inline classes are defined to take up no space if they contain no
> members. This was explicitly stated in the initial idea for them. So
> talking about how to allow empty types to take up no space in other
> types is part and parcel of that. Especially if it can be spun off
> into its own feature (which is much more likely to pass).
Not all types are created equal. "enum types" are not the same as "class
types" and follow different rules, so it's not such a stretch to imagine
there might be some other "inline class type" category with its own
rulebook.
(Start of original thread:
http://permalink.gmane.org/gmane.comp.lang.c++.isocpp.proposals/20049)
On 2015-08-30 19:05, Nicol Bolas wrote:
> One of the principle failings of all properties-through-objects classes in
> C++ is that the property is an object and must obey the rules of C++
> objects. Like taking up space in the class that contains them.
>
> Inline classes are defined to take up no space if they contain no members.
> This was explicitly stated in the initial idea for them.
Wrong:
> On 2015-08-26 13:54, Matthew Woehlke wrote:
>> An inline class [...] has no storage
I didn't said they "take up no space /if they contain no members/"
(emphasis added). I said they take up no space, *period*.
> So talking about how to allow empty types to take up no space in
> other types is part and parcel of that. Especially if it can be spun
> off into its own feature (which is much more likely to pass).
You haven't explained why inline classes aren't usable for these other
purposes.
I didn't said they "take up no space /if they contain no members/" (emphasis added). I said they take up no space, *period*.
On Monday 31 August 2015 14:53:56 Matthew Woehlke wrote:I didn't said they "take up no space /if they contain no members/" (emphasis added). I said they take up no space, *period*.The problem with taking up no space is that you can't have two of them in a class, because their (empty) subobjects would have the same address. You simply can't have that, period.
It sounds to me that the actual class would just be an "adaptor" or "view" of the outer class.
For example, an implementation could make it so the object carries the "this" pointer to the outer. Something like: class Outer { int m_value; public: // syntax potentially conflicts with inline variables inline struct { decltype(*this) &operator=(int newValue) { this->m_value = value; } operator int() const { return this->m_value; } void *operator &() const = delete; } value; /* if the syntax is a problem, we can use: auto value = inline struct { ... }; */ };
inline struct { decltype(*this) &operator=(int newValue) { this->class.m_value = value; return *this; } operator int() const { return this->class.m_value; } } value;
Where the *real* inline struct would be: struct _RealInline { Outer *this; }; But if I were implementing this, I would make the ABI avoid the double indirection and not store a pointer to the this pointer, but the pointer directly, as you would an Outer *.
Questions: * can you actually obtain a reference to the property? Note that decltype(*this): does that expand to Outer or to the inner type?
* if it's to the outer type, then this could be surprising: (obj.value = 1) = 2; * if it's to the inner type, then you can bind a reference to it: decltype(auto) v = (obj.value = 1); and form a pointer even though operator& was disallowed: auto ptr = std::addressof(v);
* a third option is for operator= to return a prvalue (int) or void. Note that decltype(*this) in that context is currently not allowed, so you couldn't get the type of an anonymous struct that easily (but not impossible).
On 2015-09-01 13:27, Vicente J. Botet Escriba wrote:I believe that inline classes can be useful independently of whether its size if 0 when there is no data. While this could have its importance for properties without storage, it seems less important to me when using inline classes as services or subjects of an object as them use to carry some data.I fail to see the value of an "inline" class vs. a normal class if they *don't* provide the zero-storage guarantee. At that point, all you've done is avoid the back-reference pointer to the containing class.
Which you could do anyway with pointer arithmetic, which moreover should be equally efficient as if it was a compiler feature. Conversely, trying to maintain the utility of inline classes *without* the zero-storage guarantee makes them significantly more complicated to implement.
On 01 Sep 2015, at 20:15 , Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:I don't see the trouble copying inline class instances between two outer class instances if they are of the good type.
Con:I believe that this is orthogonal and related to the possibility of having object of size 0, which should be taken by another proposal.
* If empty inline subobjects have no storage then multiple instances can have the same address.* In the context of properties (under the assumption they are implemented with this technique) it is potentially a leaking abstraction. Other languages don't allow you to pass around the actual property itself, only the result of their getter. But then, these languages usually don't have the concept of references. It might be an interesting tool to pass a property by-reference to a function and have that function operate on the property instead of only its value. But it poses the ambiguity of when to deduce the property object and when the property's value.I don't seethe problem. please could you clarify?
Lets say no for the time being.
*(4)* Should empty inline subobjects have storage?Pro: zero overheadyes.
Con: see (3)
*(5)* Should separate inline subobjects be allowed to have the same type?Pro: It's convenient.Right.
Con:
* If they aren't empty and access their state in member functions the compiler must find a way to distinguish them. Either by storing a hidden offset or by instantiating different actual types. Both options aren't zero overhead either in object size or code size.* If every inline subobject has a different type and (3) is resolved as yes then it is possible to safely static_cast between the containing instance and the inline subobject.I don't understand this cons.
Why do you deduce that the property must be copied? Shouldn't T deduce to X&.
However
void foo(A::X);
must be ill formed.
foo(a.y); // also ill-formed but shouldn't be: the intention is to pass a.y's valueI don't see the advantage of having properties that behave as raw data member, but this is out of the scope of the thread.
A possible way of clearing this ambiguity is with a contextual(?) keyword
class A {
// x is not supposed to be a property
inline class X { ... } x;
// y is supposed to be a property
// "property int" replaces the "inline class..." boilerplate
property int y { /* default implementation get/set auto-generated where not user-provided */ };
};
template<class T> void foo(T);
A a;
foo(a.x); // ill-formed, cannot copy inline class
foo(a.y); // instantiation ill-formed at first, but it's a property so try again with the implicit int conversion
When it comes to references, then as already stated the property type is expected to be implicitly convertible to/assignable from an instance of its underlying type. So even if argument deduction were to prefer the inline class's type, the property still behaves under duck typing as-if it were the value. Concretely in the following code:
tempalte<class T>
void foo(T& prop) { prop = 5; }
It should be irrelevant whether T deduces to "int" or "MyHealthProperty<Player, 32>".
But it is more difficult with pointers. Whereas you can assign an int& to a MyHealthProperty<Player, 32>& you cannot assign a int* to a MyHealthProperty<Player, 32>*. Therefore it makes a pretty big difference whether one deduces int* or MyHealthProperty<Player, 32>*. But taking the address of the result of a property's getter is only possible if it doesn't return an rvalue so this remains an open problem.
Well that took longer than expected. And now I realize that this still overlaps with the properties thread. I guess that won't change unless it is decided that inline classes are not the right tool to implement them. Sigh.
Thanks for the long post.I see two major issues up to now:
* managing with several instances of the same inline class implies either having different types or including a run-time overhead.
* passing inline class instances by reference transforms any such function in a template if the OFFSET is part of the type.
Storing the offset on the inline class solves both issues, but introduce a new one :(
Vicente
--
--- You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Am 01.09.2015 um 21:12 schrieb Matthew Woehlke:
> On 2015-09-01 14:06, Miro Knejp wrote:
>> Even if only empty types are considered to begin with we can at least
>> anticipate non-empty inline classes in the future and not
>> artificially restrict our future selves.
> I still disagree with making things that don't look like templates
> suddenly behave like templates any more than necessary. It's one thing
> for multiple instances of the same type to become implicitly templates
> because of the offset magic needed to make their data members distinct.
> It's quite another thing for a freestanding class *that isn't a
> template* (at least, doesn't *look* like one) to turn into a template
> (i.e. not have its code compiled until it is actually instantiated).
So what? The Concepts TS introduces functions that don't *look* like
templates and yet are templates. Most of the time I don't need to care
whether I'm calling a template function or instantiating a template
class or not. That's the beauty of it.
In your suggestion, you can't even forward-declare an implicit template inline class. The decision of whether it's a template or not is based on the definition. So in the previous example, the compiler has to go
has to go through 2 whole member function declarations before it finds one that depends on the parent, and thus makes it a template.
On Tuesday, September 1, 2015 at 4:15:45 PM UTC-4, Miro Knejp wrote:Am 01.09.2015 um 21:12 schrieb Matthew Woehlke:
> On 2015-09-01 14:06, Miro Knejp wrote:
>> Even if only empty types are considered to begin with we can at least
>> anticipate non-empty inline classes in the future and not
>> artificially restrict our future selves.
> I still disagree with making things that don't look like templates
> suddenly behave like templates any more than necessary. It's one thing
> for multiple instances of the same type to become implicitly templates
> because of the offset magic needed to make their data members distinct.
> It's quite another thing for a freestanding class *that isn't a
> template* (at least, doesn't *look* like one) to turn into a template
> (i.e. not have its code compiled until it is actually instantiated).
So what? The Concepts TS introduces functions that don't *look* like
templates and yet are templates. Most of the time I don't need to care
whether I'm calling a template function or instantiating a template
class or not. That's the beauty of it.
Concepts gets away with it because the decision as to whether it's a template function or not is made in the declaration. If none of the parameter types are a concept, then it's not a template function.
In your suggestion, you can't even forward-declare an implicit template inline class. The decision of whether it's a template or not is based on the definition. So in the previous example, the compiler has to go
I'm not sure if we want compilers to have to do that. Let alone have users do it.
I do not like the idea of using inline classes as a means to solve every little bugbear of the language. If you don't like explicitly having to use the CRTP, then a fix for that should be a separate proposal.
Am 01.09.2015 um 23:35 schrieb Nicol Bolas:
On Tuesday, September 1, 2015 at 4:15:45 PM UTC-4, Miro Knejp wrote:Of course I can.Am 01.09.2015 um 21:12 schrieb Matthew Woehlke:
> On 2015-09-01 14:06, Miro Knejp wrote:
>> Even if only empty types are considered to begin with we can at least
>> anticipate non-empty inline classes in the future and not
>> artificially restrict our future selves.
> I still disagree with making things that don't look like templates
> suddenly behave like templates any more than necessary. It's one thing
> for multiple instances of the same type to become implicitly templates
> because of the offset magic needed to make their data members distinct.
> It's quite another thing for a freestanding class *that isn't a
> template* (at least, doesn't *look* like one) to turn into a template
> (i.e. not have its code compiled until it is actually instantiated).
So what? The Concepts TS introduces functions that don't *look* like
templates and yet are templates. Most of the time I don't need to care
whether I'm calling a template function or instantiating a template
class or not. That's the beauty of it.
Concepts gets away with it because the decision as to whether it's a template function or not is made in the declaration. If none of the parameter types are a concept, then it's not a template function.
In your suggestion, you can't even forward-declare an implicit template inline class. The decision of whether it's a template or not is based on the definition. So in the previous example, the compiler has to go
inline class P;
There, forward declared an inline class. The definiton doesn't affect whether it's an implicit template or not. My approach implies that every inline class is incomplete and an implicit template because the true type of "this" is unknown, including empty inline classes without any offset magic. Whether the compiler decides to only instantiate a single specialization under the as-if rule or not I see as a QoI issue.
Why is everyone so hung up on the CRTP comparison?
I'm not sure if we want compilers to have to do that. Let alone have users do it.
I do not like the idea of using inline classes as a means to solve every little bugbear of the language. If you don't like explicitly having to use the CRTP, then a fix for that should be a separate proposal.
Why is everyone so hung up on the CRTP comparison?
I'm not sure if we want compilers to have to do that. Let alone have users do it.
I do not like the idea of using inline classes as a means to solve every little bugbear of the language. If you don't like explicitly having to use the CRTP, then a fix for that should be a separate proposal.
Because your concept relies upon an implicit use of CRTP to make your code functional.
Every use of an inline class requires access to the containing class. And your idea requires template instantiation, that declaring an inline class member implicitly passes the class type to that inline class, performing template instantitation.
The CRTP is usually used for base classes, but it can also be used for member types. Indeed, without the implicit CRTP usage you suggest, users would be required to explicit use the CRTP when using derived inline classes, if the base class is to be able to access members.
Therefore, your idea is an implicit use of CRTP. The comparison is perfectly reasonable.
Am 02.09.2015 um 00:44 schrieb Nicol Bolas:
On Tuesday, September 1, 2015 at 6:04:51 PM UTC-4, Miro Knejp wrote:Am 01.09.2015 um 23:35 schrieb Nicol Bolas:
Yes, it *relies* on the pattern as an implementation detail, it is *not* a language tool for CRTP. In many implementations virtual functions rely on function pointer tables. That doesn't make virtual functions a language tool for function pointer tables. The feature wording doesn't have to mention templates or CRTP at all *if* it isn't required to describe the observable behavior.Why is everyone so hung up on the CRTP comparison?I'm not sure if we want compilers to have to do that. Let alone have users do it.
I do not like the idea of using inline classes as a means to solve every little bugbear of the language. If you don't like explicitly having to use the CRTP, then a fix for that should be a separate proposal.
Because your concept relies upon an implicit use of CRTP to make your code functional.
inline class X;
X *p = ...;
I don't mind the comparison. I am puzzled by why people demand the CRTP aspect to somehow become its own proposal when I use CRTP as an implementation detail, not an advertized feature.Every use of an inline class requires access to the containing class. And your idea requires template instantiation, that declaring an inline class member implicitly passes the class type to that inline class, performing template instantitation.
The CRTP is usually used for base classes, but it can also be used for member types. Indeed, without the implicit CRTP usage you suggest, users would be required to explicit use the CRTP when using derived inline classes, if the base class is to be able to access members.
Therefore, your idea is an implicit use of CRTP. The comparison is perfectly reasonable.
(Though, I'll note, I also don't allow naming the
type of a freestanding inline class. However that was more for
consistency with sizeof being illegal for the same, and because I don't
see a use case.)
inline class Base {void Func() {...}};
class Foo
{
inline class Derived : public Base
{
void Func() {Base::Func();}
}
};
template<typename T> inline class Base<T> {...};
template<typename T> void Process(Base<T> &ref);
On 2015-09-02 12:13, Nicol Bolas wrote:
> On Wednesday, September 2, 2015 at 11:18:18 AM UTC-4, Matthew Woehlke wrote:
At which point, they become inline *members* :-). Although at that point
I'm less convinced that "inline" is the correct term, plus it is much
more likely to conflict with the inline variables proposal. Suggestions?
At any rate, this would imply that an inline member can be any type, so
long as the type is empty (which, practically speaking, means an empty
class). Moreover, it means these are equivalent:
class Empty {};
class Dog { inline Empty member; }
class Cat { inline class {} member; }
class Cow { class Empty {}; inline Empty member; }
(Note that the 'inline' - or other keyword - is now required before the
type name of an "inline member".)
This also makes it more palatable to me to separate EMO and inner
classes. Would there be a problem allowing 'inner' as a contextual
keyword if followed by 'class' or 'struct'?
class outer
{
class nest inner
{
};
};
class outer
{
class inner //which is it?
{
};
};
There is still an issue at
the proposal level, however, as the two remain intertwined; i.e. inline
inner classes need additional stipulations that depend on both features.
>> There is still an issue at the proposal level, however, as the two
>> remain intertwined; i.e. inline inner classes need additional
>> stipulations that depend on both features.
>
> True. But so long as empty members can be made to work, I don't think there
> are any language issues with inner classes, whether they are empty or not.
The trouble is that non-empty inner classes must either a) store as a
member, or b) encode into the generated code via "magic" type
replication, the offset to the parent class in order to correctly access
the members.
(I won't rehash the pros and cons of each approach.)
However, we specifically want empty inner classes to always use the
second option (and implicitly, to have the same code for all copies,
since the offset in all cases is 0). Expressing that (potentially, i.e.
if we don't go with option (b)) requires that the inner class proposal
is aware of empty members.
Note that I'm less in favor of (b) because it involves magical type
creation, and because it precludes arrays of inner classes. (Note also,
I'm assuming offsets, because offsets don't preclude relocation and/or
trivial copying.)
T arr[5];
T *pArr = arr;
pArr + 3 == &arr[3];
> (though that brings up a new question of behavior: if B were derived
> from A, could B create new instances of A's inner classes?).
This wouldn't work for *virtual* bases. For non-virtual bases, I don't
see a problem, or a reason to forbid it.
> However, it makes no sense for a user to explicitly invoke the
> copy/move action of an inner class member, so that can be forbidden.
> You can copy/move the owning class, but not an inner class member
> directly.
I want to say "huh?", but I think I am not following you.
Are you talking about forbidding a copy/move action of an inner class
type that occurs outside the context of the containing class's copy/move
ctor? (That is, no passing inner class types by value?) If yes, then agreed.
class Outer
{
<<inducer>> class Inner {...};
Inner a, b;
};
Outer out;
out.b = out.a; //Explicitly invoke copy assignment.
out.b = std::move(out.a); //Explicitly invoke move assignment.
On 2015-09-03 22:44, Nicol Bolas wrote:
> On Thursday, September 3, 2015 at 5:11:00 PM UTC-4, Matthew Woehlke wrote:
>> On 2015-09-03 15:21, Nicol Bolas wrote:
>>> However, it makes no sense for a user to explicitly invoke the
>>> copy/move action of an inner class member, so that can be forbidden.
>>> You can copy/move the owning class, but not an inner class member
>>> directly.
>>
>> Are you talking about forbidding a copy/move action of an inner class
>> type that occurs outside the context of the containing class's copy/move
>> ctor? (That is, no passing inner class types by value?) If yes, then
>> agreed.
>
> I'm talking about this:
>
> class Outer
> {
> <<inducer>> class Inner {...};
> Inner a, b;
> };
>
> Outer out;
> out.b = out.a; //Explicitly invoke copy assignment.
> out.b = std::move(out.a); //Explicitly invoke move assignment.
>
> Things like that should not be allowed.
Why not? An *assignment operator* doesn't have anything to do with
storage; the target object already exists, and the source object doesn't
cease to exist. Even a move ctor is required to leave the source object
in a *valid* state.
<<inducer>> class Inner
{
...
int32_t __offset;
};
> Similarly, you shouldn't be able to copy/move construct them (which
> could only happen within a constructor's initialization list or an
> NSDM initializer).
You definitely *should* be able to copy/move construct them in the
context of the outer class's ctors.
> So... what happens if you do `memcpy(&out.b, &out.a,
> sizeof(Outer::Inner))`? That's right, you copy `out.a.__offset` into
> `out.b.__offset`. So now `out.b` has the wrong offset.
>
> That's bad.
So... don't do that :-). That's clearly UB, same as if you wrote
directly into a class's vptr or other such nonsense.
The compiler should have no problem making the necessary adjustments so
that a relocating copy ctor for an inner class does not clobber the offset.
> I understand the need for it, but it's coming dangerously close to saying
> that stateful inner classes interfere with trivial copyability.
>
> A trivial copy constructor should be equivalent to a memcpy.
See above. If we state that the outer class is responsible for
maintaining the offsets (which makes sense; the offset is an artifact of
the inner class's location in the outer class, *not* the inner class's
own data), and that the inner class's copying does not touch the offset,
then I think we are okay. (Sure, this means that the ownership model
is... unusual. But I don't see an *implementation* problem.)
It also occurs to me that since we aren't legislating the
implementation, this is all implementation details...
Of course, you could fix this by forbidding arrays of inner classes altogether ;)
Trivially copyable is behavior; it's something you have to specify in the standard. And if that behavior cannot be implemented, then that behavior cannot be permitted by the standard.
So it's not an implementation detail. The need to implement it is getting in the way of the desired behavior.
On 2015-09-04 14:38, Nicol Bolas wrote:
> On Friday, September 4, 2015 at 1:51:56 PM UTC-4, Matthew Woehlke wrote:
>> On 2015-09-04 12:51, Nicol Bolas wrote:
>>> So... what happens if you do `memcpy(&out.b, &out.a,
>>> sizeof(Outer::Inner))`? That's right, you copy `out.a.__offset` into
>>> `out.b.__offset`. So now `out.b` has the wrong offset.
>>>
>>> That's bad.
>>
>> So... don't do that :-). That's clearly UB, same as if you wrote
>> directly into a class's vptr or other such nonsense.
>
> Ah, but there's the difference: classes with vptrs are not trivially
> copyable. If you're saying that you can't memcpy it, then it's not
> trivially copyable (ie: the entire point of the trivially copyable notion).
> Which is something we don't want to do unless there's no other choice.
>
>> The compiler should have no problem making the necessary adjustments so
>> that a relocating copy ctor for an inner class does not clobber the offset.
>
> Absolutely. But it would not be a *trivial* copy constructor
> <http://en.cppreference.com/w/cpp/language/copy_constructor#Trivial_copy_constructor>
> anymore.
Why not? The offset is an implementation detail, therefore it is not a
member for the purpose of standardese.
The trick is to specify that when
the compiler performs a trivial copy of an inner class instance to a
*different* instance of the same type (e.g. in a default copy ctor),
that the offset bytes must be excluded from that copy.
I'm still not convinced this is a(n insurmountable) problem. If it's the
*same* member (but of a different containing instance), then the offset
is the same and can be copied also, so the case of the outer class being
trivially copied is not broken. If it's a *different* member, you just
can't group doing copies of multiple consecutive inner classes into a
single memmove.
On 2015-09-06 19:04, Nicol Bolas wrote:
> OK, given the various discussions on this thread, I've put together an
> alternate look at this
Thanks! (Saved me the trouble ;-)...)
> The sizeof a stateless class is not affected by the fact that it is stateless
Question: this would imply that, for:
struct Foo
{
<<stateless>> class { ... } bar;
int a;
};
...sizeof(Foo::bar) != 0. Is that going to be a problem? (Note that e.g.
a naïve attempt to memcpy 'bar' from one Foo to another will clobber
Foo::a as a result.) Is this just a case of "don't do that"?
class empty {};
class derived1 : public empty
{
int foo;
};
class derived2 : public empty
{
float bar;
};
> Variables which are arrays of stateless types cannot be declared.
> Arrays of stateless types can still be allocated with new[]. (This
> could be limited solely to arrays declared as NSDM's, but it's
> cleaner to just forbid them altogether.)
This seems inconsistent; why forbid automatic storage but allow dynamic
(heap) storage? Why not either allow arrays where a single instance
would also have storage (i.e. non-NSDM's), or forbid them entirely?
Oh, and this made me think of another interesting question: does a
static member of stateless type have storage?
> Inner class members can be copied/moved exactly as any other type.
> [note: Specifically, if a stateful inner class member is trivially
> copyable, it should be legal to do a memcpy(dest, src, sizeof(T)) on
> it into another inner class member of the same type.]
(...and further discussed in Implementation.)
I think this can be broken down into three cases:
1. Trivial copyability as part of copying the containing class.
2. Trivial copyability as performed by a compiler-generated function.
3. Trivial copyability as the user might wish to manually accomplish.
For #2 and #3, I mean specifically in instances that aren't covered by
#1, i.e. copying (an) inner class member(s) outside of copying the
entire containing class. I don't see a problem with cases #1 and #2.
What's the problem with declaring #3 to be UB?
> Pointers and references to inner class members can be converted to
> point to their owning class instance via static_cast. Such
> conversions can also be implicit, much like converting from a
> derived class to a base class
I'm not sure about this. I mean, I see no technical problem, but at one
point I had it in mind to forbid this for the sake of encapsulation. I
wouldn't say I feel strongly about it, however.
> If a pointer/reference to an inner class names a member of an owning class
That said, I'm rather less enamored with this. For one, it allows
writing such nonsense as:
class Outer { <<inner>> class { ... } a, b; int c; };
C c;
c.a.b.a.b.a.a.a.b.b.a.b.a.c = 5;
> So forbidding arrays of inner classes of any kind is not too far afield.
It occurs to me that this would be more palatable if we had something to
declare what "looks" like an array of inner classes despite being
something different under the hood.
> The outer class cannot be trivial however, since its default
> constructor (and every other non-copy/move constructor) will need to
> initialize this hidden data.
I suggest adding to this a note that this is true for any type with
non-stateless inner classes.
> Inner classes can be used for properties.
I, of course, would also include my "as-a interface" example here.
For
that matter, since we're talking about (possibly) stateful inner
classes, the "service object" example is also worth mentioning.
The more
use cases, the better :-).
I might also add: "(with zero overhead in the case of stateless inner
classes)".
> To do that, we could define the concept of an "inner class template".
> That is, one could declare an inner class outside the scope of a
> class, but only explicitly as a template. One of the template
> parameters would be the owning class type. And thus, name accesses
> that could implicitly use this would be considered dependent
> accesses.
I'll also reiterate that I think CRTP in general could use some love;
inner classes would provide increased incentive for that.
I'll also go
so far as to say I see nothing in the above that appears to me to be
specific to use of CRTP with inner classes. Why limit it to inner classes?
> I see no reason to declare this to be undefined behavior. It should either
> be allowed or forbidden.
I'm not talking about the *copy ctor*; the compiler can easily generate
that to copy only the non-offset bits. I'm talking about *user code*
doing a memcpy/memmove. The suggestion would be that for the *user* to
call memcpy/memmove on an inner class's bytes is UB.
>>> Pointers and references to inner class members can be converted to
>>> point to their owning class instance via static_cast. Such
>>> conversions can also be implicit, much like converting from a
>>> derived class to a base class
>>
>> I'm not sure about this. I mean, I see no technical problem, but at one
>> point I had it in mind to forbid this for the sake of encapsulation. I
>> wouldn't say I feel strongly about it, however.
>
> Orthogonality. The compiler has to be able to make that conversion anyway.
> And the limits on inner classes mean that if an inner class
> pointer/reference is valid, such conversions are reasonable (ie: their
> owning objects also exist).
True, but then, the compiler can cast from a derived class to a private
base. As I said, the reason to deny it is for encapsulation, i.e.
reasons similar to disallowing outside access to a private member.
Keep in mind, we can always forbid it now and change our minds later. We
can't go the other way.
For that matter, we could allow outer member access only via
'this', which would prevent anything but one layer of self recursion
(i.e. 'this->a.b' where 'this' == '&this->a').
>>> So forbidding arrays of inner classes of any kind is not too far afield.
>>
>> It occurs to me that this would be more palatable if we had something to
>> declare what "looks" like an array of inner classes despite being
>> something different under the hood.
>
> Define "looks".
Has an operator[](size_t). In fact, that's pretty much the only
requirement I'd place on it.
>> For that matter, since we're talking about (possibly) stateful
>> inner classes, the "service object" example is also worth
>> mentioning.
>
> I don't know what that means. Can you give a more specific example?
See Edward Catmur's reply¹ to your request for use cases in the previous
thread. As I understand, this would be useful when you have several
"iterations" of essentially the same functionality, but with different
purposes. For example, a stream writer that needs to access the parent
class, which might be instantiated for stdout and stderr, and used for
different purposes (e.g. normal operation, errors).
(¹ http://permalink.gmane.org/gmane.comp.lang.c++.isocpp.proposals/20142)
On 2015-09-09 15:50, Nicol Bolas wrote:
> On Monday, September 7, 2015 at 4:08:58 PM UTC-4, Matthew Woehlke wrote:
>> Oh, and this made me think of another interesting question: does a
>> static member of stateless type have storage? What happens then
>> when I take the address of such a critter? Is itallowed to be
>> null?
>
> The same thing that happens when you take the address of any stateless
> object, whether a member or not.
>
> You get a pointer of the stateless type, who's memory address may be shared
> with other objects. For a stateless NSDM, it may have the value of its
> owner's `this`. But it may also point to `this` + 1 byte. Or to something
> else entirely.
Okay, let me rephrase that; is reading data via that pointer UB? If yes,
then I guess we have no problem here.
>> On 2015-09-07 15:04, Nicol Bolas wrote:
>>> I see no reason to declare this to be undefined behavior. It should
>>> either be allowed or forbidden.
>>
>> I'm not talking about the *copy ctor*; the compiler can easily generate
>> that to copy only the non-offset bits. I'm talking about *user code*
>> doing a memcpy/memmove. The suggestion would be that for the *user* to
>> call memcpy/memmove on an inner class's bytes is UB.
>
> There's a term for types where memcpy results in UB: *not trivially
> copyable*.
Okay... where is that specified?
And, more importantly, why would the
world end if we tweaked that definition for inner classes?
>>>> It occurs to me that this would be more palatable if we had
>>>> something to declare what "looks" like an array of inner
>>>> classes despite being something different under the hood.
>>>
>>> Define "looks".
>>
>> Has an operator[](size_t). In fact, that's pretty much the only
>> requirement I'd place on it.
>
> Then it's not an array. And if you declare one in the exact same way as a
> language array, yet defy the rules of language arrays, then you'll confuse
> people needlessly.
Oh, good grief, no! The *most* the declaration would "look like an
array" would be:
std::inner_class_array<InnerType, 4> props;
More likely it would look like:
InnerType p0, p1, p2, p3;
std::inner_class_array props = { p0, p1, p2, p3 };
Basically, the idea is you have several instances of
similar-but-not-identical "sub-objects" that need close access to the
outer object. Inner classes allow you to factor out the common pieces
into a class (possible a base class) and have multiple instances as
members of the outer class.
On 2015-09-10 22:04, Nicol Bolas wrote:
> On Wednesday, September 9, 2015 at 5:58:26 PM UTC-4, Matthew Woehlke wrote:
>> Okay, let me rephrase that; is reading data via that pointer UB? If yes,
>> then I guess we have no problem here.
>
> What do you mean by "reading data"?
Dereferencing the pointer in a way that causes a read access to memory.
(Or write, for that matter.)
> The object is stateless. Just like a
> struct with no members, it has no (non-stateless) NSDMs to "read".
Stateless* src = ...;
memcpy(somewhere, src, sizeof(Stateless);
Is that not UB?
On 2015-09-11 13:12, Nicol Bolas wrote:
> On Friday, September 11, 2015 at 10:57:05 AM UTC-4, Matthew Woehlke wrote:
>> Stateless* src = ...;
>> memcpy(somewhere, src, sizeof(Stateless);
>>
>> Is that not UB?
>
> Using the rules I outlined, it depends. If `src` is a member subobject,
> then it's UB. If `somewhere` is a member subobject, then it's UB. If
> neither are member subobjects, then it's fine.
So...
void foo()
{
static Stateless s;
char buf[sizeof(Stateless)];
memcpy(buf, &s, sizeof(Stateless));
}
...is the above well-formed and not UB?
Then, going back to my original
question:
>> Oh, and this made me think of another interesting question: does a
>> static member of stateless type have storage? I want to say "yes" (or
>> possibly "implementation defined"), so that you can do things like take
>> its address.
...the answer would I think have to be "yes"?
>> We have two possible implementations: offset part of inner class
>> type, and offset adjacent to inner class type. One precludes
>> trivially copyability, the other arrays, both of which seem
>> desirable features. That seems to me like a case of
>> "unimplementable without [new limitations]" / "necessary".
>>
>> An exception/clarification to the memcpy rule could permit both.
>
> And you lose the *whole point* of trivial copyability: the ability to do a
> memcpy on it.
I'm not sure I agree that *the user* being able to do a memcpy is "the
whole point of trivial copyability". I would say rather that *the
compiler* being able to do so is of main importance.
An exception doesn't break the critical use cases:
- The outer class, including inner class members, may still be
trivially copied, including via naïve memcpy.
- The inner class copy/move/assign may still have a default generated
implementation that is a simple memcpy.
...and the user can still copy *one* inner class type (but not an array
thereof) via memcpy, albeit the arguments must be non-standard.
Hmm... I guess the problem you are concerned about is:
template <typename T>
legacy_code(T& out, T const& in)
{
// ...
if (/* T is trivially copyable */)
{
memcpy(&out, &in, sizeof(T));
}
// ...
}
...?
(And why are you even writing such code?