[std-proposals] Inline Classes (was "Properties" support in C++)

410 views
Skip to first unread message

Klaim - Joël Lamotte

unread,
Aug 30, 2015, 3:13:11 PM8/30/15
to std-pr...@isocpp.org


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.
 
 
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.

It's a feature proposal. Anything that don't already exists can be proposed (and be rejected or not). 
Like having a way to reuse this kind of code.
I'm not suggesting a specific solution, just pointing to a side effect of the feature
that needs a solution.

 
On 30 August 2015 at 13:51, Miro Knejp <miro....@gmail.com> wrote:
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.
 

Nicol Bolas

unread,
Aug 30, 2015, 7:05:20 PM8/30/15
to ISO C++ Standard - Future Proposals
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.
 
 
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.

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. 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).

Miro Knejp

unread,
Aug 30, 2015, 7:56:29 PM8/30/15
to std-pr...@isocpp.org
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.

Klaim - Joël Lamotte

unread,
Aug 30, 2015, 8:14:52 PM8/30/15
to std-pr...@isocpp.org
On 31 August 2015 at 01:05, Nicol Bolas <jmck...@gmail.com> wrote:
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.
 

I did include Miro's explanation in the end of the first email of this discussion thread. 
 
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.
 

I guess it would be good to focus on that anyway yes.

Nicol Bolas

unread,
Aug 30, 2015, 9:08:43 PM8/30/15
to ISO C++ Standard - Future Proposals
On Sunday, August 30, 2015 at 7:56:29 PM UTC-4, Miro Knejp wrote:
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?

... Are you seriously asking why it's undesirable by users for a member that doesn't contain anything, one that exists solely for syntactic sugar, to take up space in a type? This is genuinely a question you need an answer to?

Here's the thing: you can make an implementation of properties in C++ right now. You can define a property class and give it a bunch of overloaded operators that make it work in almost exactly the way you want inline classes to work.

The problem is that these implementations impose overhead, both syntactic and semantic. They break trivial copyability, since they need a `this` pointer to the owner. Getting access to that `this` pointer requires rather unnatural code (when implementing your getter/setter). Implementing getters/setters often requires lambdas or other syntax kludges. Properties make the class bigger needlessly. And so forth.

This overhead, both syntactic and semantic, makes people disinclined to use them for properties. That's why you see people still asking for properties in C++; because the library solution is a non-starter.

Inline classes were proposed for the sole purpose of avoiding that overhead. Their job was to make a library solution for properties viable. Therefore, if they can't even make properties work without overhead... what good are they?

So yes, empty inline class members taking up space is a problem.

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.

I never said that they're impossible. What I'm saying is that you are arbitrarily ransacking the language. And thus far, the only justification provided for this is to make it easier to implement a niche feature. And even with this language ransacking, the abstraction leaks; the fact that a property is being used is visible to the user.

I don't think that inline classes are worth the hassle of making them actually work. Or more to the point, you have not yet proven that this inline clases are sufficiently useful to justify the sheer number of language changes that will be necessary in order to make this feature work.

Language features aren't free, and the more of the language the feature affects, the more it costs. You have to pay for them by having the feature provide something that is genuinely useful to people. So please, give some justifications for what problems users have that this feature will solve.

And no, "we can get properties" doesn't count. We already have a thread for that.
 
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.

What you seem to be saying is that the language ramifications of a proposal should not even be considered by the people proposing it. That we should simply ignore the innumerable basic rules of C++ that have to be worked around in order for the proposal to work.

I don't agree with that. If it takes 5 minutes of thought to break a proposal, then it was not sufficiently well thought out enough to make in the first place.

>
> 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.

OK so... what is the rulebook?

This is your idea. This is your proposal (not necessarily all you personally. I'm invoking the "royal you", meaning those who want it). What should this "own rulebook" actually be?

Because thus far, we haven't seen one. Oh, we've seen bits of one. We have some general notions. But we have not yet seen a fully-formed idea, one that actually goes through at least the obvious language changes that need to happen for them to work.

That's why this shouldn't have gotten a separate thread; there isn't a proposal yet.

Matthew Woehlke

unread,
Aug 31, 2015, 2:54:18 PM8/31/15
to std-pr...@isocpp.org
(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*.

>> This inline class can contain member definitions as usual.

Maybe this is the cause of the confusion? I mean member *functions*,
there, not *data* members. (Data members don't have "definitions", but I
can see where that could be unclear.)

Also, no virtual members. (Come to think of it, no ctors/dtors, either,
which actually clarifies several issues. Those don't make sense, since
there is no storage to create or destroy.)

That said, static (data) members should be no problem.

> 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.

--
Matthew

Bjorn Reese

unread,
Aug 31, 2015, 3:26:01 PM8/31/15
to std-pr...@isocpp.org
I frequently come across use-cases where inline classes can be useful.

One use-case is to compensate for the lack of partial template
specialization of class member functions. A workaround is to introduce
a functor with static member functions, which are passed the class
instance as a parameter, because the functor needs to operate on the
class member variables. For example:

class value
{
public:
template <typename T>
T convert() const;
};

Say that need a specialization for value::convert<T>() for integral
types. This can be done via the following functor:

template <typename T, typename Enable = void>
struct value_functor {};

template <typename T>
struct value_functor<T, enable_if<is_integral<T>>::type>
{
static T convert(const value& v)
{
// Do integral conversion here
}
};

This is invoked as follows:

template <typename T>
T value::convert() const
{
return value_functor<ReturnType>::convert(*this);
}

Furthermore, if value_functor must access private members in the value
class, then it must become a friend of the class.

An inline class, as I have understood it, will handle the above case
more elegantly.

Nicol Bolas

unread,
Aug 31, 2015, 4:35:07 PM8/31/15
to ISO C++ Standard - Future Proposals

Please show what the inline class equivalent of this code would look like.

Nicol Bolas

unread,
Aug 31, 2015, 4:42:40 PM8/31/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Monday, August 31, 2015 at 2:54:18 PM UTC-4, Matthew Woehlke wrote:
(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*.

Either way you say it, it leads to one fact:

The language must define the possibility of a member object that has no space. This is not a possibility that existed in C++ before, so inline classes must introduce that concept in order for them to work. It has to work through all of the problems surrounding allowing objects in other objects to take up no space.

And if you're going to go through the trouble of defining the concept, I see no reason not to extend it to more arbitrary classes.

It's the same amount of work either way. It's just that, if its a separate construct, then it can be more useful, since it's not bound to the limitations of inline classes.

> 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.

Because inline classes are explicitly bound to the classes that they are nested within. And I quote (emphasis added):

> An inline class is a special case of a nested class-type which itself has no storage, and for which "this" refers to the parent class which must contain the inline class as a member.

If I just want a freestanding class that will take up no room, regardless of which class it is used within, I can't do that with inline classes. Since it's not a nested class.

Thiago Macieira

unread,
Aug 31, 2015, 5:01:37 PM8/31/15
to std-pr...@isocpp.org
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 { ... };
*/
};

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).

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Matthew Woehlke

unread,
Aug 31, 2015, 5:21:38 PM8/31/15
to std-pr...@isocpp.org
On 2015-08-31 17:01, Thiago Macieira wrote:
> 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.

Er... yes. They don't act like regular "objects" in the sense the
language standard uses that term.

> It sounds to me that the actual class would just be an "adaptor" or "view" of
> the outer class.

Well... yes! That's exactly what I was getting at with the color-type
example.

> 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; }

-Werror=return-type! You forgot 'return *this;' ;-). (Which I mention
mainly because I would further hope that you can just write the return
type as 'auto' and let it be deduced from the 'return *this;'.)

> 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 { ... };
> */

Yes, that should also work, and might be preferable stylistically. If we
have to allow only that form, I have no problem. (Or... using some
totally other syntax, as I've mentioned repeatedly :-).)

> };
>
> Questions:
> * can you actually obtain a reference to the property? Note that
> decltype(*this): does that expand to Outer or to the inner type?

Yes, you can obtain a reference to the property / inline struct. The
type is Outer::Inner&, but the memory address is the same as the Outer&.

> * 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);

Er... where/why was operator& disallowed? I don't recall doing so
(though I admit my recollection may be faulty). It seems that one should
be allowed to do so; the type of the pointer is Outer::Inner*, with bits
equal to the Outer*.

--
Matthew

Bjorn Reese

unread,
Aug 31, 2015, 5:33:17 PM8/31/15
to std-pr...@isocpp.org
On 08/31/2015 10:35 PM, Nicol Bolas wrote:

> Please show what the inline class equivalent of this code would look like.

I just provided a use-case. I will leave the syntax of inline classes
to you guys.

Nicol Bolas

unread,
Aug 31, 2015, 5:37:11 PM8/31/15
to ISO C++ Standard - Future Proposals

The code you showed was what you had to do before inner classes. I don't understand how that would translate to inner classes. So I'm asking you to show how they would be used to provide equivalent functionality.

Thiago Macieira

unread,
Aug 31, 2015, 6:21:32 PM8/31/15
to std-pr...@isocpp.org
On Monday 31 August 2015 17:21:23 Matthew Woehlke wrote:
> > decltype(*this) &operator=(int newValue)
> > { this->m_value = value; }
>
> -Werror=return-type! You forgot 'return *this;' ;-). (Which I mention
> mainly because I would further hope that you can just write the return
> type as 'auto' and let it be deduced from the 'return *this;'.)

But if this is Outer*, return *this would be Outer&, not Outer::Inner&.

> > Questions:
> > * can you actually obtain a reference to the property? Note that
> > decltype(*this): does that expand to Outer or to the inner type?
>
> Yes, you can obtain a reference to the property / inline struct. The
> type is Outer::Inner&, but the memory address is the same as the Outer&.
>
> > * 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);
>
> Er... where/why was operator& disallowed?

Because I added:
void *operator &() const = delete;

> I don't recall doing so
> (though I admit my recollection may be faulty). It seems that one should
> be allowed to do so; the type of the pointer is Outer::Inner*, with bits
> equal to the Outer*.

Another question:
* How do I call a function in Outer that has the same name as one in the
inner struct? Suggestion:

void set(int newValue)
{ Outer::set(newValue); }

In other words, the inner, inline class behaves as if it derived from the
outer class.

Thiago Macieira

unread,
Aug 31, 2015, 6:22:46 PM8/31/15
to std-pr...@isocpp.org
On Monday 31 August 2015 15:21:28 Thiago Macieira wrote:
> In other words, the inner, inline class behaves as if it derived from the
> outer class.

Protected inheritance.

Miro Knejp

unread,
Aug 31, 2015, 9:01:29 PM8/31/15
to std-pr...@isocpp.org
Am 31.08.2015 um 20:53 schrieb Matthew Woehlke:
I didn't said they "take up no space /if they contain no members/" (emphasis added). I said they take up no space, *period*.
It was me who said that and I believe this is an unnecessary limitation.

Let me try to recap all that was being suggested or discussed so far in this and the property thread and propose some solutions of my own. This may not be an exhaustive list.

(1) An inline subobject is only instantiable and copyable as part of the containing object besides user defined conversion functions. Rules for compiler-generated functions for the containing class behave as-if the inline subobject was a regular subobject. Rules for deriving properties like standard layout and triviality behave as-if the inline subobject was a regular subobject.

(2) Name lookup in the inline subobject behaves as-if the inline subobject was derived from the containing class.

(3) Should it be possible to obtain a pointer/reference to the inline subobject itself?
Pro:
* It can be passed as reference/pointer around on its own and be involved in type deduction as long as it is not attempted to make a copy of it outside of its containing class type.
Con:
* 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.

(4) Should empty inline subobjects have storage?
Pro: zero overhead
Con: see (3)

(5) Should separate inline subobjects be allowed to have the same type?
Pro: It's convenient.
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.

(6) It should be possible to declare re-usable inline classes outside of the containing class. This means these inline classes defined at namespace scope must be based on template instantiations as they are only complete types when embedded in a containing class and final name lookup and offset-patching can take place.

Here are my attempts at solving some of the above problems.
Keep in mind that this is largely pseudocode so please don't get riled up on the syntax again.

(6) I'll give this a try first because it is not dependent on others:

inline class P {
  int i;
public:
  void set(int j) { i = j; }
  int get() { return i; }
  void bar() { this->baz(); }
};
In a definition like the above "this" is always dependent because its type is unknown until the inline class is embedded in an actual containing class. This is the same name lookup behavior as in classes with a dependent base class, making it consistent with existing practice and the suggested name lookup for inline subobjects. It is basically syntax for compiler-generated CRTP (which is a useful pattern on its own and this might actually make it more accessible).
It is then used like
class A {
  P x; // Instantiate P<A, 0> with containing class=A and offset=0
  void baz();
};
A a;
a.x.bar(); // results in a.baz()

Because the so defined inline class is incomplete and inherently a template a function involving such a type by reference or pointer (assuming (3) is allowed) in its signature becomes a template function.

(5) My answer here is no, there cannot be two inline subobjects of the same type.

The problem:
class B {
  P x, y;
  void baz();
};
B b;
auto& x = b.x;
auto& y = b.y;
x.get();
y.get();
This poses a conflict because x and y have different offsets. The compiler has to either store the offset inside b.x and b.y or generate different types for x and y with different get/set implementations. The latter results in references to B::x and B::y to be incompatible, which is surprising because from the code they seem to have the same.

My proposed solution is to introduce a new type for each inline class if you try to re-use some shared inline type.
class C {
  inline class X : public P { } x; // Instantiate P<C, 0>
  inline class Y : public P { } y; // Instantiate P<C, 4>
  inline class Z {
    // ...
  } z1, z2; // ill-formed: only one instance per type
  void baz();
};
C c;
auto& x = c.x;
auto& y = c.y;
x.get();
y.get();
Now there is no longer an ambiguity and it is clearly evident from the fact that X and Y are distinct types in the code. If someone cames up with a terser syntax that doesn't require repeating the inheritence chain in every place, that would be awesome.

class D {
  inline class : public P { } x, y; // ill-formed: only one inline subobject per type
  inline class : public P { } x*; // ill-formed
  inline class : public P { } x[5]; // probably ill-formed because (&x[0] - this) != (&x[1] - this)
};

(3) If I were to ignore the possibility of properties then I'd be in favor of obtaining references/pointers to inline classes. For outside code duck typing makes them behave just like references to regular subobjects. A problem still remains with the address equality of multiple empty inline subobjects if they had zero size.

If viewed under the light of a possible implementation for properties then it may become a leaking abstraction. However I believe there is as much value in passing a property by-reference as is passing a regular value by-reference. It allows the callee modifying access (whether that is good or bad is left undecided). Assuming properties were implicitly convertible to the underlying type (by whichever means) then a function expecting the underlying type invokes the conversion, whereas argument deduction results in the inline subobject backing the property. This can cause ambiguity depending on whether the inline class represents a property or not.

class A {
  // x is not supposed to be a property
  inline class X { ... } x;
  // y is supposed to be a property
  inline class Y { operator int(); Y& operator=(int); ... } y;
};
template<class T> void foo(T);
A a;
foo(a.x); // ill-formed, cannot copy inline class
foo(a.y); // also ill-formed but shouldn't be: the intention is to pass a.y's value

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.

Jeremy Maitin-Shepard

unread,
Sep 1, 2015, 12:05:53 AM9/1/15
to ISO C++ Standard - Future Proposals
It seems the discussion here is touching on a bunch of a features that would be potentially very useful:

1. Zero-size class data members.

2. Java-style inner classes: this could be done by allowing the definition of classes with lambda-style capture specifications.  It could be allowed as a special case that if only a single reference or non-mutable pointer is captured and there are no data members, then the extra level of indirection is skipped, such that a pointer to the inner class is exactly the captured pointer.  Maybe this same optimization could also be supported in other cases.

3. Expression aliases, so that a property proxy object can be accessed using x.property rather than x.property().

In my view combining (2) and (3) to get properties is much cleaner, and brings us a lot more generally useful functionality, than the inline class idea.  (1) wouldn't be needed for properties but would certainly save a lot of metaprogramming hassle generally.

Matthew Woehlke

unread,
Sep 1, 2015, 9:42:46 AM9/1/15
to std-pr...@isocpp.org
On 2015-08-31 18:21, Thiago Macieira wrote:
> On Monday 31 August 2015 17:21:23 Matthew Woehlke wrote:
>>> decltype(*this) &operator=(int newValue)
>>> { this->m_value = value; }
>>
>> -Werror=return-type! You forgot 'return *this;' ;-). (Which I mention
>> mainly because I would further hope that you can just write the return
>> type as 'auto' and let it be deduced from the 'return *this;'.)
>
> But if this is Outer*, return *this would be Outer&, not Outer::Inner&.

It's a little odd, because decltype(this) is Outer::Inner*, but you can
access the members of Outer through it. (Well... Inner can. Presumably
they are protected in that context, however. IOW, what you said in your
next message :-).)

>>> Questions:
>>> * can you actually obtain a reference to the property? Note that
>>> decltype(*this): does that expand to Outer or to the inner type?
>>
>> Yes, you can obtain a reference to the property / inline struct. The
>> type is Outer::Inner&, but the memory address is the same as the Outer&.
>>
>>> * 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);
>>
>> Er... where/why was operator& disallowed?
>
> Because I added:
> void *operator &() const = delete;

Ah, didn't see that. Nevertheless, I will repeat the question: *why* did
you do that? Do you wish to prevent taking the address of this specific
type?

(In general I don't think you can prevent std::addressof.)

>> I don't recall doing so
>> (though I admit my recollection may be faulty). It seems that one should
>> be allowed to do so; the type of the pointer is Outer::Inner*, with bits
>> equal to the Outer*.
>
> Another question:
> * How do I call a function in Outer that has the same name as one in the
> inner struct? Suggestion:
>
> void set(int newValue)
> { Outer::set(newValue); }

That's how I'd do it, yes.

> In other words, the inner, inline class behaves as if it derived from the
> outer class.

I think yes. I'm not sure I would have phrased it exactly that way
(maybe I will, now that you mention it), but I believe that, yes, the
effect is the same.

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 10:57:15 AM9/1/15
to std-pr...@isocpp.org
On 2015-08-31 21:02, Miro Knejp wrote:
> *(3)* Should it be possible to obtain a pointer/reference to the inline
> subobject itself?
>
> Con:
> * If empty inline subobjects have no storage then multiple instances can
> have the same address.

This is one reason to look at them not as subobjects but as derived
types. Consider:

class Foo { ... };
class Bar : protected Foo { ... };

I can have a Foo* and a Bar* pointing to the same actual object, which
are identical. This is very similar to the case of inline classes,
except that I can have many inline classes that are siblings rather than
an inheritance chain. In both cases, all 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 think the loophole that needs to be closed here is ensuring that
trying to take a property by value calls a conversion operator. Taking a
property by reference should be transparently equivalent to taking its
value, except that the call to the getter gets shifted. (And potentially
called more than once.)

Come to think of it, this (lvalue conversion) may be a general problem
not limited to inline class properties.

> *(4)* Should empty inline subobjects have storage?

Yes, as that's one of the purposes of the feature.

> *(5)* Should separate inline subobjects be allowed to have the same type?
> Pro: It's convenient.
> Con:
> * If they aren't empty and access their state in member functions the
> compiler must find a way to distinguish them.

...which is why I wouldn't permit non-empty inline classes. Basically,
as soon as you allow them to be non-empty, it's hard to see how you can
retain any benefit from them being inline vs. degenerating into plain
old concrete classes.

> * 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.

If every object has a different type, I don't see the benefit to them
having (non-static data) members in the first place. Just move the
members you want to put in them to the containing concrete class.

> *(6)* It should be possible to declare re-usable inline classes outside
> of the containing class. This means these inline classes defined at
> namespace scope must be based on template instantiations as they are
> only complete types when embedded in a containing class and final name
> lookup and offset-patching can take place.

They don't need to be templated. They just act like namespaces.

> inline class P {
> int i;
> public:
> void set(int j) { i = j; }
> int get() { return i; }
> void bar() { this->baz(); }
> };

Assuming that this is the complete context (which I believe was your
intent), this code will not compile, because P has no member 'baz'.
There is no delayed look-up. If you want to use this as a base class
e.g. for a property, use CRTP and reinterpret_cast 'this' to the derived
type.

Given a freestanding inline class, the only things you can access with
'this' are member functions of that class and its base inline classes
(if any), and static members of the same.

> It is basically syntax for compiler-generated CRTP (which is a useful
> pattern on its own and this might actually make it more accessible).

...which is something I'd like to see, but orthogonally. If we make CRTP
better, we shouldn't limit the improvement to inline classes.

> *(5)* My answer here is no, there cannot be two inline subobjects of the
> same type.
>
> The problem:
> class B {
> P x, y;
> void baz();
> };
> B b;
> auto& x = b.x;
> auto& y = b.y;
> x.get();
> y.get();
> This poses a conflict because x and y have different offsets. The
> compiler has to either store the offset inside b.x and b.y or generate
> different types for x and y with different get/set implementations. The
> latter results in references to B::x and B::y to be incompatible, which
> is surprising because from the code they seem to have the same.
>
> My proposed solution is to introduce a new type for each inline class if
> you try to re-use some shared inline type.
> class C {
> inline class X : public P { } x; // Instantiate P<C, 0>
> inline class Y : public P { } y; // Instantiate P<C, 4>
> inline class Z {
> // ...
> } z1, z2; // ill-formed: only one instance per type
> void baz();
> };

I don't see why the first should work but the second should be illegal.

That said, I think this could work, but I'm nervous about the amount of
"magic" involved. Maybe it would be better to try to get inline classes
in as storage-free and later improve them to permit storage?

> inline class : public P { } x*; // ill-formed

I'm not sure I agree (assuming you meant '*x'). Pointers to inline
classes are legal, so why not allow a class member that is such a pointer?

class Foo
{
public:
inline class : public P {} *x = _x;

private:
decltype(*x) _x;
}

I don't know *why* you'd ever do this, but I don't see a problem.

> inline class : public P { } x[5]; // probably ill-formed because
> (&x[0] - this) != (&x[1] - this)

This should fall under the same rule as 'P x, y'; see comments above.
(Pretty useless with storage-free inline classes, but...)

> If viewed under the light of a possible implementation for properties
> then it may become a leaking abstraction. However I believe there is as
> much value in passing a property by-reference as is passing a regular
> value by-reference. It allows the callee modifying access (whether that
> is good or bad is left undecided).

Assuming of course that the reference is non-const, in which case one
MUST pass the property reference anyway.

Repeating myself a bit here, but I see two possibilities: either the
callee wants a mutable reference, and so the only way to make the call
is to pass a reference to the property anyway, or else it takes a const
reference and passing the property reference could anyway only change
the program semantics if the callee uses the value multiple times, and
the value changes in the mean time.

> Assuming properties were implicitly convertible to the underlying
> type (by whichever means)

...which is sort of the point of why I defined the getter as a
conversion operator :-). (Notice I never made it explicit.)

> argument deduction results in the inline subobject backing the
> property.

Probably we would want to develop techniques to trigger conversion of a
property-like inline class as a deduced type. If derived from
std::property, this shouldn't be difficult. We almost certainly want
also a type trait to identify inline classes. The trick would be
automagically identifying the type of a single conversion operator.

(Including a utility class for this purpose in the proposal probably
wouldn't be amiss...)

> class A {
> // x is not supposed to be a property
> inline class X { ... } x;
> // y is supposed to be a property
> inline class Y { operator int(); Y& operator=(int); ... } y;
> };
> template<class T> void foo(T);
> A a;
> foo(a.x); // ill-formed, cannot copy inline class
> foo(a.y); // also ill-formed but shouldn't be: the intention is to pass
> a.y's value

I'm inclined to believe that attempting to copy/assign an inline class
in a deduced context should attempt to invoke a conversion operator.

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 11:05:08 AM9/1/15
to std-pr...@isocpp.org
How is (3) relevant? If you implement properties like (2), then I assume
you are still implementing them something like an inner class with
get/set methods to represent the property. In which case, wouldn't that
class just be a member?

--
Matthew

Miro Knejp

unread,
Sep 1, 2015, 1:00:03 PM9/1/15
to std-pr...@isocpp.org

> On 01 Sep 2015, at 16:56 , Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>
>> inline class P {
>> int i;
>> public:
>> void set(int j) { i = j; }
>> int get() { return i; }
>> void bar() { this->baz(); }
>> };
>
> Assuming that this is the complete context (which I believe was your
> intent), this code will not compile, because P has no member 'baz'.
> There is no delayed look-up. If you want to use this as a base class
> e.g. for a property, use CRTP and reinterpret_cast 'this' to the derived
> type.

You know, it helps reading the entire section before responding. Please don’t just blindly comment on a snippet of code without considering the explanation:
"In a definition like the above "this" is always dependent because its type is unknown until the inline class is embedded in an actual containing class. This is the same name lookup behavior as in classes with a dependent base class, making it consistent with existing practice ...”

There *is* delayed lookup because I said so. It’s part of the solution. “this" is dependent in the the above definition because I say so, therefore "this->baz()" lookup is delayed until instantiation. The compiler has to internally generate some CRTP-like template construct to make it work.

>
>> If viewed under the light of a possible implementation for properties
>> then it may become a leaking abstraction. However I believe there is as
>> much value in passing a property by-reference as is passing a regular
>> value by-reference. It allows the callee modifying access (whether that
>> is good or bad is left undecided).
>
> Assuming of course that the reference is non-const, in which case one
> MUST pass the property reference anyway.
Unless the property converts to an lvalue-reference.
>
> Repeating myself a bit here, but I see two possibilities: either the
> callee wants a mutable reference, and so the only way to make the call
> is to pass a reference to the property anyway, or else it takes a const
> reference and passing the property reference could anyway only change
> the program semantics if the callee uses the value multiple times, and
> the value changes in the mean time.
You have the same problems with regular references already. The value of a reference you hold could change between accesses.

>
> I'm inclined to believe that attempting to copy/assign an inline class
> in a deduced context should attempt to invoke a conversion operator.
This is some very special behavior and I think it should be reserved for things explicitly marked as properties. Otherwise you cannot reliably SFINAE on inline classes that don’t represent properties.

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 1:27:10 PM9/1/15
to std-pr...@isocpp.org

Le 31/08/15 23:01, Thiago Macieira a écrit :
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.
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 propose to let this point out of the current discussion and see what could be done even if the size is not empty.



It sounds to me that the actual class would just be an "adaptor" or "view" of 
the outer class. 
Yes this is the use case I have in mind. I use to call them services or subjects as they are always related to a given object.
IMO we should be able to take the address of an inline class instance, I see no problem with that.

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 { ... };
	*/
};
I would suggest to use this to refer to the inline class instance and this->class when referring to the outer class instance.
As an inline class instance exists only on the scope of an outer class the compiler could always know about the address of this->class.
Your example could be rewritten as
	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 *.
I would not use a pointer as each inline class instance could have an implicit offset respect to its outer class instance.

Questions:
 * can you actually obtain a reference to the property? Note that 
decltype(*this): does that expand to Outer or to the inner type?
I think we should be able. It seems to me that making the difference between this and this->class inside a inline class would allow it.

decltype(*this) is the inner inline class
decltype(*this->class) is the outer class



 * 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);
This is something that you can already do with any class that disables the operator&, isn't it?
Note that I'm not for disabling it the operator& implicitly for inline classes.

 * 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).

If we make abstraction of the empty issue, the inline class I have in mind could be emulated as follows

struct A
{
  template <int OFFSET>
  friend struct X : inline_class<A, OFFSET>
  {
    void f() { this_class().x =0; }
    ...
    int val;
  };
  X<0> prop1; //note that the offsets should be calculated by the compiler
  X<sizeof<int>> prop2;
  int x;
};

inline_class<A, OFFSET> defines the function this_class depending on the OFFSET parameter.

More is needed to allow assigning from two instances of the same inline class. I let this an exercise.
Note that this copy can be trivial when the members are the inline class instance doesn't stores a pointer to the outer class.  

This emulation is off course error prone as the user must replace the compiler when giving the valid offsets.

Vicente


Vicente J. Botet Escriba

unread,
Sep 1, 2015, 1:34:28 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 00:21, Thiago Macieira a écrit :
> On Monday 31 August 2015 17:21:23 Matthew Woehlke wrote:
>>>
>
> Another question:
> * How do I call a function in Outer that has the same name as one in the
> inner struct? Suggestion:
>
> void set(int newValue)
> { Outer::set(newValue); }
>
> In other words, the inner, inline class behaves as if it derived from the
> outer class.
>
Or

void set(int newValue)
{ this->class.set(newValue); }

if we adopt this->class to refer to the outer class.

Vicente

Matthew Woehlke

unread,
Sep 1, 2015, 1:45:32 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 12:59, Miro Knejp wrote:
>> On 01 Sep 2015, at 16:56 , Matthew Woehlke wrote:
>>> inline class P {
>>> int i;
>>> public:
>>> void set(int j) { i = j; }
>>> int get() { return i; }
>>> void bar() { this->baz(); }
>>> };
>>
>> Assuming that this is the complete context (which I believe was your
>> intent), this code will not compile, because P has no member 'baz'.
>> There is no delayed look-up. If you want to use this as a base class
>> e.g. for a property, use CRTP and reinterpret_cast 'this' to the derived
>> type.
>
> You know, it helps reading the entire section before responding. Please don’t just blindly comment on a snippet of code without considering the explanation:
> "In a definition like the above "this" is always dependent because its type is unknown until the inline class is embedded in an actual containing class. This is the same name lookup behavior as in classes with a dependent base class, making it consistent with existing practice ...”

I did read the whole thing, and I stand by what I said (if not how I
said it).

Assuming that P is a freestanding inline class (that is, you did not
omit a concrete class which contains the definition of P), then the
example is (read: "IMO, should be") ill-formed, because P has no member
'baz'.

I disagree that this should magically become well-formed when you have a
member of type P inside some class that has a 'baz'. What I was trying
to say is that I... am not comfortable with your suggestion that the
above is magically a CRTP template. If you want CRTP-like behavior...
use CRTP.

That said, I would be wholly in favor of an *orthogonal* proposal to
make CRTP easier... but it should work for non-inline-class cases also,
and it should still require some sort of decoration.

>>> If viewed under the light of a possible implementation for properties
>>> then it may become a leaking abstraction. However I believe there is as
>>> much value in passing a property by-reference as is passing a regular
>>> value by-reference. It allows the callee modifying access (whether that
>>> is good or bad is left undecided).
>>
>> Assuming of course that the reference is non-const, in which case one
>> MUST pass the property reference anyway.
>
> Unless the property converts to an lvalue-reference.

Er... well, sure, except that I can't imagine how the property doing
that isn't broken :-).

Let me rephrase that: if the callee wants a mutable reference, then
almost certainly the callee *needs* to accept the property itself by
reference, not a reference to the underlying value. So, in that case,
you *want* the "leaky abstraction".

In any other case, either the conversion gets invoked anyway and there
is no "leak", or you aren't passing a mutable property anyway and there
is no semantic change and so the "leak" doesn't matter.

That's what I meant... that I don't see an actual problem here.

>> Repeating myself a bit here, but I see two possibilities: either the
>> callee wants a mutable reference, and so the only way to make the call
>> is to pass a reference to the property anyway, or else it takes a const
>> reference and passing the property reference could anyway only change
>> the program semantics if the callee uses the value multiple times, and
>> the value changes in the mean time.
>
> You have the same problems with regular references already. The value
> of a reference you hold could change between accesses.

True, but in the non-property case, a correctly designed API won't ever
give you a reference.

>> I'm inclined to believe that attempting to copy/assign an inline class
>> in a deduced context should attempt to invoke a conversion operator.
>
> This is some very special behavior and I think it should be reserved
> for things explicitly marked as properties. Otherwise you cannot
> reliably SFINAE on inline classes that don’t represent properties.

Understood. I admit this area is a little tricky, and is probably best
dealt with by providing a way for the programmer to explicitly indicate
that a class should be subject to such conversion.

If "properties" always derived from std::property, we could also go by
that, but I would prefer to not require that. (There would also be a
type trait to ask about an inline class type, but that's probably not
adequate on its own.)

I think my current plan is to punt on this for now.

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 1:52:36 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 13:34, Vicente J. Botet Escriba wrote:
> Le 01/09/15 00:21, Thiago Macieira a écrit :
>> Another question:
>> * How do I call a function in Outer that has the same name as one in
>> the
>> inner struct? Suggestion:
>>
>> void set(int newValue)
>> { Outer::set(newValue); }
>>
>> In other words, the inner, inline class behaves as if it derived from the
>> outer class.
>
> Or
>
> void set(int newValue)
> { this->class.set(newValue); }
>
> if we adopt this->class to refer to the outer class.

We *could* do that, but it means additional syntax change for minimal gain.

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 2:02:49 PM9/1/15
to std-pr...@isocpp.org
On 2015-08-30 15:13, Klaim - Joël Lamotte wrote:
> My suggestion was to discuss this outside the properties thread because
> it's impossible to follow with the several sub-discussions ongoing.

Also per Nicol's request, I'm attaching¹ my current draft. This isn't
exactly formal proposal yet (in particular, there is no standardese at
all), but I'm trying to at least start in the direction of a more formal
paper rather than just paragraphs in an e-mail.

The "zero storage object" rationale is... "lacking", since I'm less
familiar with that one. I'd appreciate help filling that out.

Comments encouraged, of course. Also, please remind me what bits I
forgot about, as I'm sure there are some.

(¹ I hope to get this on github eventually, but I don't have the ability
to access my account at the moment.)

Thanks,

--
Matthew
inline-class.rst

Matthew Woehlke

unread,
Sep 1, 2015, 2:05:07 PM9/1/15
to std-pr...@isocpp.org
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.

--
Matthew

Miro Knejp

unread,
Sep 1, 2015, 2:06:09 PM9/1/15
to std-pr...@isocpp.org

> On 01 Sep 2015, at 19:45 , Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>
> I disagree that this should magically become well-formed when you have a
> member of type P inside some class that has a 'baz'. What I was trying
> to say is that I... am not comfortable with your suggestion that the
> above is magically a CRTP template. If you want CRTP-like behavior...
> use CRTP.
If you have this->foo() in a class with a dependent base class it also magically becomes well-formed when instantiated. If we assume name lookup in inline classes works as-if they were derived from the parent, this is one consequence of that. I don’t see the problem. The fact it can be realized by the compiler with a CRTP-like mechanism is an implementation detail. All the standard needs to say is that the type of “this” is dependent until the inline class is added to a parent, at which point two-phase lookup is performed. Even if we omit the two-phase lookup thing, the compiler still has to do *some* CRTP magic for non-empty inline classes.

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.
>
> True, but in the non-property case, a correctly designed API won't ever
> give you a reference.
A correctly designed API won’t do many things. If it’s not ill-formed it needs to be specified.

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 2:15:58 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 03:02, Miro Knejp a écrit :
> Am 31.08.2015 um 20:53 schrieb Matthew Woehlke:
>> I didn't said they "take up no space /if they contain no members/"
>> (emphasis added). I said they take up no space, *period*.
> It was me who said that and I believe this is an unnecessary limitation.
>
> Let me try to recap all that was being suggested or discussed so far
> in this and the property thread and propose some solutions of my own.
> This may not be an exhaustive list.
>
> *(1)* An inline subobject is only instantiable and copyable as part of
> the containing object besides user defined conversion functions. Rules
> for compiler-generated functions for the containing class behave as-if
> the inline subobject was a regular subobject. Rules for deriving
> properties like standard layout and triviality behave as-if the inline
> subobject was a regular subobject.
Agreed.
>
> *(2)* Name lookup in the inline subobject behaves as-if the inline
> subobject was derived from the containing class.
Frindship should work also.
>
> *(3)* Should it be possible to obtain a pointer/reference to the
> inline subobject itself?
yes.
> Pro:
> * It can be passed as reference/pointer around on its own and be
> involved in type deduction as long as it is not attempted to make a
> copy of it outside of its containing class type.
I don't see the trouble copying inline class instances between two outer
class instances if they are of the good type.
> Con:
> * If empty inline subobjects have no storage then multiple instances
> can have the same address.
I believe that this is orthogonal and related to the possibility of
having object of size 0, which should be taken by another proposal.
> * 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?
>
> *(4)* Should empty inline subobjects have storage?
Lets say no for the time being.
> Pro: zero overhead
> Con: see (3)
>
> *(5)* Should separate inline subobjects be allowed to have the same type?
yes.
> Pro: It's convenient.
> 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.
Right.
> * 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.
> *(6)* It should be possible to declare re-usable inline classes
> outside of the containing class. This means these inline classes
> defined at namespace scope must be based on template instantiations as
> they are only complete types when embedded in a containing class and
> final name lookup and offset-patching can take place.
I would say this will be nice, but I could live without.
>
> Here are my attempts at solving some of the above problems.
> Keep in mind that this is largely pseudocode so please don't get riled
> up on the syntax again.
>
> *(6)* I'll give this a try first because it is not dependent on others:
>
> inline class P {
> int i;
> public:
> void set(int j) { i = j; }
> int get() { return i; }
> void bar() { this->baz(); }
> };
> In a definition like the above "this" is always dependent because its
> type is unknown until the inline class is embedded in an actual
> containing class. This is the same name lookup behavior as in classes
> with a dependent base class, making it consistent with existing
> practice and the suggested name lookup for inline subobjects. It is
> basically syntax for compiler-generated CRTP (which is a useful
> pattern on its own and this might actually make it more accessible).
> It is then used like
> class A {
> P x; // Instantiate P<A, 0> with containing class=A and offset=0
> void baz();
> };
> A a;
> a.x.bar(); // results in a.baz()
>
> Because the so defined inline class is incomplete and inherently a
> template a function involving such a type by reference or pointer
> (assuming (3) is allowed) in its signature becomes a template function.
Agreed.
>
>
> *(5)* My answer here is no, there cannot be two inline subobjects of
> the same type.
>
> The problem:
> class B {
> P x, y;
> void baz();
> };
> B b;
> auto& x = b.x;
> auto& y = b.y;
> x.get();
> y.get();
> This poses a conflict because x and y have different offsets. The
> compiler has to either store the offset inside b.x and b.y or generate
> different types for x and y with different get/set implementations.
> The latter results in references to B::x and B::y to be incompatible,
> which is surprising because from the code they seem to have the same.
You are right that they are incompatible in the current standard if
different types are generated. We need to find a specific solution for
inline classes.
>
> My proposed solution is to introduce a new type for each inline class
> if you try to re-use some shared inline type.
> class C {
> inline class X : public P { } x; // Instantiate P<C, 0>
> inline class Y : public P { } y; // Instantiate P<C, 4>
> inline class Z {
> // ...
> } z1, z2; // ill-formed: only one instance per type
> void baz();
> };
> C c;
> auto& x = c.x;
> auto& y = c.y;
> x.get();
> y.get();
> Now there is no longer an ambiguity and it is clearly evident from the
> fact that X and Y are distinct types in the code.
IMHO, this doesn't solves the problem.
> If someone cames up with a terser syntax that doesn't require
> repeating the inheritence chain in every place, that would be awesome.
>
> class D {
> inline class : public P { } x, y; // ill-formed: only one inline
> subobject per type
> inline class : public P { } x*; // ill-formed
> inline class : public P { } x[5]; // probably ill-formed because
> (&x[0] - this) != (&x[1] - this)
> };
>
> *(3)* If I were to ignore the possibility of properties then I'd be in
> favor of obtaining references/pointers to inline classes. For outside
> code duck typing makes them behave just like references to regular
> subobjects. A problem still remains with the address equality of
> multiple empty inline subobjects if they had zero size.
>
> If viewed under the light of a possible implementation for properties
> then it may become a leaking abstraction. However I believe there is
> as much value in passing a property by-reference as is passing a
> regular value by-reference. It allows the callee modifying access
> (whether that is good or bad is left undecided). Assuming properties
> were implicitly convertible to the underlying type (by whichever
> means) then a function expecting the underlying type invokes the
> conversion, whereas argument deduction results in the inline subobject
> backing the property. This can cause ambiguity depending on whether
> the inline class represents a property or not.
>
> class A {
> // x is not supposed to be a property
> inline class X { ... } x;
> // y is supposed to be a property
> inline class Y { operator int(); Y& operator=(int); ... } y;
> };
> template<class T> void foo(T);
> A a;
> foo(a.x); // ill-formed, cannot copy inline class
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 value
>
> 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 */ };
I don't see the advantage of having properties that behave as raw data
member, but this is out of the scope of the thread.
> };
> 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

Jeremy Maitin-Shepard

unread,
Sep 1, 2015, 2:18:44 PM9/1/15
to std-pr...@isocpp.org
As I see it, sizeof(CapturingClass) would be the size of a pointer to the outer class, so you couldn't embed it as a zero-size member.

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 2:20:29 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 20:02, Matthew Woehlke a écrit :
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. 
Well this is already a good thing. But not only, the inline class makes clear the intent..

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.

Could you explain why?

Vicente

Matthew Woehlke

unread,
Sep 1, 2015, 2:28:52 PM9/1/15
to std-pr...@isocpp.org
Do you mean like this?

class Value
{
public:
template <typename T> inline class Conversion
{
// ...generic implementation...
}

template <typename T> Conversion<T> convert;
};

template <typename T>
inline class value::conversion<T, enable_if<is_integral<T>>::type>
{
T operator()() const { /* access members of Value here */ }
};

Value v;
long x = v.convert<long>();

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 2:35:06 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 14:20, Vicente J. Botet Escriba wrote:
> Le 01/09/15 20:02, Matthew Woehlke a écrit :
>> Conversely, trying to maintain the utility of inline classes *without*
>> the zero-storage guarantee makes them significantly more complicated to
>> implement.
>
> Could you explain why?

I think I've addressed this sufficiently already that I'm not going to
repeat myself. The main point is that being zero-sized *is*, IMO, one of
the primary (if not *the*) motivating features. Once you allow them to
maybe-or-maybe-not be zero sized, you have to deal with all sorts of
special cases depending whether they are or not, what happens when you
have more than one instance, and so forth. In short, you either
sacrifice a major rationale or make the proposal much, much more
complicated.

--
Matthew

Miro Knejp

unread,
Sep 1, 2015, 2:40:15 PM9/1/15
to std-pr...@isocpp.org
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.
Yes, that’s fine.

Con:
* If empty inline subobjects have no storage then multiple instances can have the same address.
I believe that this is orthogonal and related to the possibility of having object of size 0, which should be taken by another proposal.
* 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?
If you have a function template<class T> void foo(const T&) then the deduction results in the inline class’s type for T. However if T represents a property then you have now exposed the property implementation detail. I don’t know if this is really a problem, but some have argued it is a leaky abstraction and thus undesireable.


*(4)* Should empty inline subobjects have storage?
Lets say no for the time being.
Pro: zero overhead
Con: see (3)

*(5)* Should separate inline subobjects be allowed to have the same type?
yes.
Pro: It's convenient.
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.
Right.
* 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.
It allows you to do 
A a;
auto& x = static_cast<A::X&>(a);
auto& y = static_cast<A::Y&>(a);
auto& a2 = static_cast<A&>(x);
auto& a3 = static_cast<A&>(y);

But only if A.x and A.y have different types. If they have the same type it only works if a.x and a.y somehow know their offset inside A.
It technically does, but it’s very verbose. I’d really like something that’s a bit more user friendly.



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.
Template parameters don’t deduce to references. You need to explicitly write foo(T&) if you want a lvalue reference parameter.

foo(a.y); // also ill-formed but shouldn't be: the intention is to pass a.y's value

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 */ };
I don't see the advantage of having properties that behave as raw data member, but this is out of the scope of the thread.
I often see properties where only the setter is user-defined and the getter is left unchanged. It has its uses. But these languages (C#, Obj-C) also have some syntax/convention to assign to the actual backing variable (which the auto-generated getter is reading) from within the setter. If we don’t get that the usefulness drops significantly.
};

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/.

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 2:46:39 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 20:33, Matthew Woehlke a écrit :
How can you ensure that an inline class has size 0 if has members?
If you can not, how having the possibility that they have size 0 when they have no members simplifies the proposal?

Vicente

Matthew Woehlke

unread,
Sep 1, 2015, 2:54:27 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 14:46, Vicente J. Botet Escriba wrote:
> How can you ensure that an inline class has size 0 if has members?

I don't. I've said, repeatedly, that it's *not allowed* to have
(non-static data) members.

--
Matthew

Nicol Bolas

unread,
Sep 1, 2015, 2:56:34 PM9/1/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Yes, that's something like what I was looking for. And it's... weird. Specifically, the definition of `Value::convert`. I'm not sure I understand what that's doing.

Value::convert is a non-static data member. And those can't be variable templates, as far as I know. So... how does that work? Besides you saying "inline class members can be variable templates".

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 3:07:22 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 20:54, Matthew Woehlke a écrit :
Sorry, I missed it. I don't know if the property example tis your, but this example has data members. If inline classes can not contain non-static data members I'm less interested in this feature as I will be unable to use them for subjects/services :(

Vicente

Vicente J. Botet Escriba

unread,
Sep 1, 2015, 3:08:28 PM9/1/15
to std-pr...@isocpp.org
Le 01/09/15 20:40, Miro Knejp a écrit :
>> 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.
> Yes, that’s fine.
>>> Con:
>>> * If empty inline subobjects have no storage then multiple instances can have the same address.
>> I believe that this is orthogonal and related to the possibility of having object of size 0, which should be taken by another proposal.
>>> * 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?
> If you have a function template<class T> void foo(const T&) then the deduction results in the inline class’s type for T. However if T represents a property then you have now exposed the property implementation detail. I don’t know if this is really a problem, but some have argued it is a leaky abstraction and thus undesireable.
I'm missing how this expose any implementation detail?
>>> *(4)* Should empty inline subobjects have storage?
>> Lets say no for the time being.
>>> Pro: zero overhead
>>> Con: see (3)
>>>
>>> *(5)* Should separate inline subobjects be allowed to have the same type?
>> yes.
>>> Pro: It's convenient.
>>> 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.
>> Right.
>>> * 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.
> It allows you to do
> A a;
> auto& x = static_cast<A::X&>(a);
Why would you want to allow this?
> auto& y = static_cast<A::Y&>(a);
> auto& a2 = static_cast<A&>(x);
> auto& a3 = static_cast<A&>(y);
>
> But only if A.x and A.y have different types. If they have the same type it only works if a.x and a.y somehow know their offset inside A.
>
>> <snip>
I have not understood the problem then.
>
>> 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.
> Template parameters don’t deduce to references. You need to explicitly write foo(T&) if you want a lvalue reference parameter.
Then it must be ill formed. If the user wants the conversion s/he would
need to explicitly state it.
>
>>> foo(a.y); // also ill-formed but shouldn't be: the intention is to pass a.y's value
>>>
>>> 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 */ };
>> I don't see the advantage of having properties that behave as raw data member, but this is out of the scope of the thread.
> I often see properties where only the setter is user-defined and the getter is left unchanged. It has its uses. But these languages (C#, Obj-C) also have some syntax/convention to assign to the actual backing variable (which the auto-generated getter is reading) from within the setter. If we don’t get that the usefulness drops significantly.
As I said this is out of the scope of this thread. inline classes could
be used to implement properties, but the syntax wouldn't be as friendly
as a specific property feature.
Vicente

Matthew Woehlke

unread,
Sep 1, 2015, 3:12:27 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 14:06, Miro Knejp wrote:
> On 01 Sep 2015, at 19:45 , Matthew Woehlke wrote:
>> I disagree that this should magically become well-formed when you have a
>> member of type P inside some class that has a 'baz'. What I was trying
>> to say is that I... am not comfortable with your suggestion that the
>> above is magically a CRTP template. If you want CRTP-like behavior...
>> use CRTP.
>
> If you have this->foo() in a class with a dependent base class it
> also magically becomes well-formed when instantiated.

Not really. The difference here is that you *have* a base class. And a
template. Your example had neither.

> If we assume name lookup in inline classes works as-if they were
> derived from the parent, this is one consequence of that.

In the example you gave, your class doesn't *have* a parent. The "as if"
is for the class *definitions*, not the instantiations. (It can't be for
the instantiations, because that would imply that the compiler generates
new code for each instantiation. Which, okay, I get that you're asking
for that anyway, but that's not what I intended.)

I intended that code generation for inline classes is the same as for
traditional classes; that is, only one instance of the code for a
non-template inline class.

> Even if we omit the two-phase lookup thing, the compiler
> still has to do *some* CRTP magic for non-empty inline classes.

Can you elaborate?

> 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).

>> On 2015-09-01 12:59, Miro Knejp wrote:
>>> On 01 Sep 2015, at 16:56 , Matthew Woehlke wrote:
>>>> Repeating myself a bit here, but I see two possibilities: either the
>>>> callee wants a mutable reference, and so the only way to make the call
>>>> is to pass a reference to the property anyway, or else it takes a const
>>>> reference and passing the property reference could anyway only change
>>>> the program semantics if the callee uses the value multiple times, and
>>>> the value changes in the mean time.
>>>
>>> You have the same problems with regular references already. The value
>>> of a reference you hold could change between accesses.
>>
>> True, but in the non-property case, a correctly designed API won't ever
>> give you a reference.
>
> A correctly designed API won’t do many things. If it’s not ill-formed
> it needs to be specified.

I don't understand where there is a problem here. If some method takes
an int&, and I pass a property with a conversion to int&, such code can
be compiled. What happens if that int changes is no more or less
specified than if I passed an int& directly. Addressing that is
completely out of scope.

If that's not what you're talking about, please restate the problem,
because I am confused...

--
Matthew

Nicol Bolas

unread,
Sep 1, 2015, 3:16:39 PM9/1/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

First, thank you for writing a concrete proposal. And more importantly, thank you for formatting it in plain text. Lots of people are too in live with throwing PDFs around these days...

Next, seeing everything laid out in one place makes something clear to me: there are really 2 distinct features at play here (note: when I say "member" here, I mean non-static member):

1) An empty class which takes up no space when it is a member of another class.

2) A Java-style inner class which is directly connected to its owner's instance and can therefore access that object's members (as well as its own).

When you combine these two in one type, you get what you call "inline classes", when nested in a class). So "inline classes" in your definition that aren't nested would just be empty classes.

By separating these concepts, you allow users to pick the functionality they want to use. If they find it useful for a Java-style nested class to have members or virtuals or whatever, why should you want to explicitly prevent that? Why build that limitation into the system? Similarly, if they find it useful to have a class that doesn't take up room that isn't a Java-style inner class, again, why should you want to stop them?

The only time these two features interact has to do with the ability to have zero-overhead combinations of the two: empty inner classes that take up no space. Because Java-style inner class implementations would naturally just stick a `this` pointer in the object, but if you want the object to not take up space, you have to do things differently, with pointer/reference trickery.

But in general, the problems incurred by #1.

What I find that I don't like about the proposal currently is that `inline class` means two different things in different contexts. Outside of a class definition, it means "empty type". Inside of one, it means "empty type that can access the surrounding object's innards".

Matthew Woehlke

unread,
Sep 1, 2015, 3:18:20 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 14:40, Miro Knejp wrote:
> I often see properties where only the setter is user-defined and the
> getter is left unchanged. It has its uses. But these languages (C#,
> Obj-C) also have some syntax/convention to assign to the actual
> backing variable (which the auto-generated getter is reading) from
> within the setter. If we don’t get that the usefulness drops
> significantly.

I'm not convinced that's an issue. If the backing data is inside the
inline class, it becomes difficult for the containing class to access it
directly without encapsulation or access protection problems. If it
isn't, it's not *that* much more typing to write 'auto get() const {
return m_value; }'. Especially if you have a PP macro to do the lifting
for you.

--
Matthew

Matthew Woehlke

unread,
Sep 1, 2015, 3:29:02 PM9/1/15
to std-pr...@isocpp.org
On 2015-09-01 14:56, Nicol Bolas wrote:
> On Tuesday, September 1, 2015 at 2:28:52 PM UTC-4, Matthew Woehlke wrote:
>> Do you mean like this?
>>
>> class Value
>> {
>> public:
>> template <typename T> inline class Conversion
>> {
>> // ...generic implementation...
>> }
>>
>> template <typename T> Conversion<T> convert;
>> };
>>
>> template <typename T>
>> inline class value::conversion<T, enable_if<is_integral<T>>::type>
>> {
>> T operator()() const { /* access members of Value here */ }
>> };
>>
>> Value v;
>> long x = v.convert<long>();
>
> Yes, that's something like what I was looking for. And it's... weird.

Yeeeaah... *this* I'd probably call a kludge :-). It's more an example
that we ought to just allow partial specialization of member functions.

> Specifically, the definition of `Value::convert`. I'm not sure I understand
> what that's doing.
>
> Value::convert is a non-static data member. And those can't be variable
> templates, as far as I know.

Um... yeah, I think you're right. I haven't yet played with a compiler
that supports template variables in any form, so I'm fumbling a bit for
how they work. (Although, the reasons they wouldn't work relate to
storage, and so wouldn't apply to inline classes. We'd have to make a
specific, additional change to allow this use case however.)

--
Matthew

Miro Knejp

unread,
Sep 1, 2015, 4:15:45 PM9/1/15
to std-pr...@isocpp.org
Am 01.09.2015 um 21:12 schrieb Matthew Woehlke:
> On 2015-09-01 14:06, Miro Knejp wrote:
>> On 01 Sep 2015, at 19:45 , Matthew Woehlke wrote:
>>> I disagree that this should magically become well-formed when you have a
>>> member of type P inside some class that has a 'baz'. What I was trying
>>> to say is that I... am not comfortable with your suggestion that the
>>> above is magically a CRTP template. If you want CRTP-like behavior...
>>> use CRTP.
>> If you have this->foo() in a class with a dependent base class it
>> also magically becomes well-formed when instantiated.
> Not really. The difference here is that you *have* a base class. And a
> template. Your example had neither.
>
>> If we assume name lookup in inline classes works as-if they were
>> derived from the parent, this is one consequence of that.
> In the example you gave, your class doesn't *have* a parent.
It does *conceptually* have one that is yet to be specified.

> The "as if"
> is for the class *definitions*, not the instantiations. (It can't be for
> the instantiations, because that would imply that the compiler generates
> new code for each instantiation. Which, okay, I get that you're asking
> for that anyway, but that's not what I intended.)
>
> I intended that code generation for inline classes is the same as for
> traditional classes; that is, only one instance of the code for a
> non-template inline class.
If the freestanding inline class doesn't contain anything that makes it
dependent on the parent's type it doens't need separate instantiations.
For me that's as a QoI issue.
>
>> Even if we omit the two-phase lookup thing, the compiler
>> still has to do *some* CRTP magic for non-empty inline classes.
> Can you elaborate?
inline classes with NSDMs need to know their offset in the parent. The
compiler needs to insert that offset magic either via instantiation or a
data field. In the former case it is equivalent to a compiler-generated
CRTP base class to retroactively modify the type/offset of "this". And
yes, I know you're still in the "only empty inline classes" world.
>
>> 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.
>
>>> On 2015-09-01 12:59, Miro Knejp wrote:
>>>> On 01 Sep 2015, at 16:56 , Matthew Woehlke wrote:
>>>>> Repeating myself a bit here, but I see two possibilities: either the
>>>>> callee wants a mutable reference, and so the only way to make the call
>>>>> is to pass a reference to the property anyway, or else it takes a const
>>>>> reference and passing the property reference could anyway only change
>>>>> the program semantics if the callee uses the value multiple times, and
>>>>> the value changes in the mean time.
>>>> You have the same problems with regular references already. The value
>>>> of a reference you hold could change between accesses.
>>> True, but in the non-property case, a correctly designed API won't ever
>>> give you a reference.
>> A correctly designed API won’t do many things. If it’s not ill-formed
>> it needs to be specified.
> I don't understand where there is a problem here. If some method takes
> an int&, and I pass a property with a conversion to int&, such code can
> be compiled. What happens if that int changes is no more or less
> specified than if I passed an int& directly. Addressing that is
> completely out of scope.
>
> If that's not what you're talking about, please restate the problem,
> because I am confused...
>
It's only related to properties and I shouldn't have brought it up in
the first place.

Nicol Bolas

unread,
Sep 1, 2015, 5:35:39 PM9/1/15
to ISO C++ Standard - Future Proposals
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.

Yet another reason for the proposal to be divided into parts: each individual part makes it clear what is being solved, and thus you can discard suggestions that are clearly out-of-bounds. Like solving CRTP.

Nicol Bolas

unread,
Sep 1, 2015, 5:37:47 PM9/1/15
to ISO C++ Standard - Future Proposals


On Tuesday, September 1, 2015 at 5:35:39 PM UTC-4, Nicol Bolas wrote:
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

Oops, forgot to finish that sentence:

has to go through 2 whole member function declarations before it finds one that depends on the parent, and thus makes it a template.

Miro Knejp

unread,
Sep 1, 2015, 6:04:51 PM9/1/15
to std-pr...@isocpp.org
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:
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
Of course I can.

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.


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? It is not meant as a tool to implement CRTP (that was just a side remark), it is a tool that *may* be used by the compiler to implent it. I used the term because people already understand how CRTP works.

Nicol Bolas

unread,
Sep 1, 2015, 6:44:07 PM9/1/15
to ISO C++ Standard - Future Proposals
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:
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
Of course I can.

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.

You can consider it whatever you like, but the language doesn't allow such ambiguity. Inline class definitions either are templates or they are not. And therefore, either every separate instantiation of that template with a different type is a separate class, or it is not.

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?

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.

Miro Knejp

unread,
Sep 1, 2015, 7:16:50 PM9/1/15
to std-pr...@isocpp.org
There is no ambiguity. As I clearly described *every freestanding inline class is an implicit template*. Period. The compiler can do under the as-if rule whatever the compiler damn pleases.


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?

Because your concept relies upon an implicit use of CRTP to make your code functional.
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.

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.
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.

Nicol Bolas

unread,
Sep 1, 2015, 8:18:28 PM9/1/15
to ISO C++ Standard - Future Proposals


On Tuesday, September 1, 2015 at 7:16:50 PM UTC-4, Miro Knejp wrote:
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:
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?

Because your concept relies upon an implicit use of CRTP to make your code functional.
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.

It is no more an "implementation detail" than C++ lambdas generating an anonymous type that has `operator()` overloaded is an "implementation detail". Those are not details that get to vary; they are observable behavior and therefore must be specified by the standard.

Here's some observable behavior: if all inline classes are templates, then this is not legal:

inline class X;

X
*p = ...;

For any `...` you might imagine.

Why is this illegal? Because there is no class X (inline or otherwise); there is a template X, which may be instantiated with template parameters to produce a multitude of classes. That's observable behavior, and therefore it must be standardized. Because if X were a real class of some kind (like the original inline class proposal suggests), then this code could potentially be legal.

To allow this to merely be an "implementation detail" which cannot be observed requires that inline classes be some fundamentally new construct, neither a class nor a template. At which point, you're going to have a hell of a lot harder time getting it standardized.

It's easy to say, "well, it just does this". It's much harder to make that actually work with the language. And if you truly want this to be an entirely new classification of construct, then you need to put forth some good-faith effort to work through the language problems it creates.
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.
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.

Whether you want it to be an implementation detail or not, once that's there, it becomes a feature. And people will use it as such; people will use these classes to avoid using the CRTP.

Furthermore, people may want to use the CRTP part without the other limitations of inline classes.

Matthew Woehlke

unread,
Sep 2, 2015, 9:45:25 AM9/2/15
to std-pr...@isocpp.org
On 2015-09-01 19:18, Miro Knejp wrote:
> 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.

Because it *is* an advertised feature, whether you call it CRTP or not.
Because making CRTP better is worthwhile in its own right and should not
be limited to inline classes. Because, as Nicol rightly points out,
making them work that way makes them significantly different from any
existing construct. (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.)

That second point is the big one; inline classes should not be a
substitute for CRTP. If one is overly tempted to use them as such - and
with your implementation, I think that would happen - then we have done
something wrong. Repeating Nicol again, the correct thing to do is make
CRTP *in general* easier, not provide an alternative with a different
set of limitations.

--
Matthew

Nicol Bolas

unread,
Sep 2, 2015, 10:33:42 AM9/2/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, September 2, 2015 at 9:45:25 AM UTC-4, Matthew Woehlke wrote:
(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.)

I found that to be quite odd in the proposal, actually. You are required to name the type of a freestanding inline class. You name it when you declare/define it, and you name it when you declare an instance of that type (as a member) or when you use it as a base class. And yet, you're allowed to name a nested inline class just fine.

I'm not sure what the point of that distinction is. You shouldn't restrict obvious syntax arbitrarily (particularly in surprising ways, since again, you're required to name them in other circumstances), simply because you don't know of a use for it.

Furthermore, there are uses for it. For example:

inline class Base {void Func() {...}};

class Foo
{
 
inline class Derived : public Base
 
{
   
void Func() {Base::Func();}
 
}
};

I see no reason to forbid this.

Then there's this:

template<typename T> inline class Base<T> {...};

template<typename T> void Process(Base<T> &ref);

The `Process` function takes any member derived from `Base`. The CRTP is used to allow static polymorphism. But all this requires that it be possible to name the base class.

Furthermore, such arbitrary restrictions only increase the standard burden of having to pick and choose the places where you're allowed to name types that have names. You should not restrict inline classes any more than is strictly necessary to get the functionality you want.

And `sizeof` is no excuse. `sizeof` for an inline class should simply be what you get for the size of any empty class. I see no reason to forbid the user from asking that question, particularly since they could be in template code or otherwise deduced areas (auto). After all, your goal is not to say that the type itself doesn't take up space. Rather, the goal is to do what the empty base optimization does: say that variables of that type don't take up space when declared as members.

Then you say that you can only declare them as members ;) So you satisfy the expected requirements of classes and objects, and you still get what you want.

Matthew Woehlke

unread,
Sep 2, 2015, 11:18:18 AM9/2/15
to std-pr...@isocpp.org
On 2015-09-02 10:33, Nicol Bolas wrote:
> On Wednesday, September 2, 2015 at 9:45:25 AM UTC-4, Matthew Woehlke wrote:
>> (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.)
>
> I found that to be quite odd in the proposal, actually. You are required to
> name the type of a freestanding inline class. You name it when you
> declare/define it, and you name it when you declare an instance of that
> type (as a member) or when you use it as a base class. And yet, you're
> allowed to name a *nested* inline class just fine.

The "trouble" is that a freestanding inline class is incomplete (in a
storage sense). Recall in particular that you cannot take its size.
Because of that, it seemed convenient to not allow using it as a
parameter either.

On the other hand, it is an artificial restriction. If being able to use
the type name despite being illegal to take its size is not considered a
problem, I have no issue allowing that.

Also see below...

> inline class Base {void Func() {...}};
>
> class Foo
> {
> inline class Derived : public Base
> {
> void Func() {Base::Func();}
> }
> };
>
> I see no reason to forbid this.

Sorry, the wording in my write-up was overly broad / poor. It was always
intended that the above is permitted. It's (only) using the type name as
a variable or parameter that would not be allowed. But...

> Then there's this:
>
> template<typename T> inline class Base<T> {...};
>
> template<typename T> void Process(Base<T> &ref);

This is *slightly* different because of the template... but on further
consideration, I think you are correct; this needs to be permitted.
(Consider e.g. the std::property<Prop> case...)

I hereby remove that restriction.

> And `sizeof` is no excuse. `sizeof` for an inline class should simply be
> what you get for the size of *any* empty class.

Here's the problem:

inline class Foo { ... };
struct Dog { Foo foo; ... };
struct Cat { Foo foo; ... };

static_assert(sizeof(Dog::foo) == sizeof(Dog));
static_assert(sizeof(Cat::foo) == sizeof(Cat));
// what is sizeof(Foo)?

I guess what you are suggesting is that the size of a FIC should be the
size of 'class {}'? (Should we explicitly say that a CIC member written
as a FIC type is actually a different type? I.e. in the above,
decltype(Dog::foo) != decltype(Foo)? If implemented as implicit
subclassing, I think this would be okay, since the sizeof an instance
already can change depending on if the visible type is a subclass or
base class. in a sense we would be codifying EBO for this case.)

--
Matthew

Matthew Woehlke

unread,
Sep 2, 2015, 12:06:44 PM9/2/15
to std-pr...@isocpp.org
On 2015-09-01 15:16, Nicol Bolas wrote:
> seeing everything laid out in one place makes something clear to me:
> there are really 2 distinct features at play here (note: when I say
> "member" here, I mean non-static member):
>
> 1) An empty class which takes up no space when it is a member of another
> class.
>
> 2) A Java-style inner class which is directly connected to its owner's
> instance and can therefore access that object's members (as well as its
> own).
>
> When you combine these two in one type, you get what you call "inline
> classes", when nested in a class). So "inline classes" in your definition
> that aren't nested would just be empty classes.

I follow what you're saying. I'm not sure I'm convinced that a change is
warranted, or if so, what would be a better approach.

For the sake of continuing discussion, let me try to rephrase what we want:

1) An empty class which takes up no space when it is a member of another
class.

2) An "inner class" which can access its containing class's members with
no runtime cost in either storage or indirection.

3) An "inner class" which can access its containing class's members and
also has members of its own.


Addressing (3) first, there are two flavors of this as I see it:

3a) The inner class is unique. Parent access can therefore be done using
a constant pointer offset.

3b) The inner class is not unique. The inner class must therefore either
i) store, in addition to its other members, a pointer to the containing
class (be that as a real pointer, or as an offset), or else ii) must be
made unique by means of the type being templated on the offset to the
containing class, thus writing different code for each instance of the
inner class.


Now, we can achieve all of the above already, although the code is
potentially very ugly, so a proposal to make those easier is strictly a
convenience; it doesn't give us anything that can't already be done.
With respect to (3b), the downside of (3bi) of course is that it
increased the required storage and adds an indirection that can't be
optimized away (relocation can still be trivial if the location of the
parent is stored as an offset rather than a complete pointer), while
(3bii) can't be used for an array of such members.


That being the case, I'm not entirely convinced that it doesn't make
sense to focus on (1) and (2), which is what the "inline class" idea as
I have presented it provides. Moreover, I don't believe that I have
precluded (3) from being added to the feature at a later time; in fact,
I have specifically made mention to it in my Future Direction section.

> By separating these concepts, you allow users to pick the functionality
> they want to use. If they find it useful for a Java-style nested class to
> have members or virtuals or whatever, why should you want to explicitly
> prevent that? Why build that limitation into the system?

I have no problem extending the feature to support that; I just prefer
to focus initially on the other use cases. I don't think that the design
precludes being extended to add this support in the future. If it does,
I would consider that a defect in the idea that should be resolved. Do
you see any such issues?

> Similarly, if they find it useful to have a class that doesn't take
> up room that *isn't* a Java-style inner class, again, why should you
> want to stop them?

This seems less relevant to me. You have access to the containing
class's members. If you don't need that, just don't use it. What's the
problem here?

> What I find that I don't like about the proposal currently is that `inline
> class` means two different things in different contexts. Outside of a class
> definition, it means "empty type". Inside of one, it means "empty type that
> can access the surrounding object's innards".

I can understand that. I'm certainly open to tweaking the syntax to make
a syntactic distinction between a FIC and a CIC. (As I've said before,
the current suggested syntax is something of a convenience and I'm not
wedded to it.)

--
Matthew

Nicol Bolas

unread,
Sep 2, 2015, 12:13:11 PM9/2/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Yes, that's the whole idea; you simply apply EBO logic to members.

It's also why I would prefer that the empty optimization be split from the rest of the CIC restrictions.

Just think about FICs for a moment. The only necessary difference between an FIC and a regular empty class is that FICs don't take up space in the class. All other restrictions are only needed for CICs.

Think about it. Is there something functionally wrong or difficult to implement if you declaring a stack variable of an FIC type? No; it's no different from declaring a stack variable of any empty class type. After all, the `this` pointer for FICs doesn't get any of the special logic that CICs get. So why create that restriction? Same goes for any other use of FICs that your current proposal restricts, relative to other types.

If you have FICs as simply empty classes, with all of the rights and privileges of any type, you can still put whatever restrictions you need on CICs. But those restrictions need not apply to FICs.

At which point, it becomes a bit difficult to justify calling them all `inline classes`. There are two separate categories of thing: empty classes and nested inline classes.

Matthew Woehlke

unread,
Sep 2, 2015, 12:15:13 PM9/2/15
to std-pr...@isocpp.org
Updated version attached. The prohibition on naming a FIC type has been
removed. I added wording to specify that a template inline class member
is permitted (this would apply only to empty inline classes if we later
add support for non-empty ones) and added partial member "function"
specialization as a use case. I also added some examples.


Open issues:

- What are some use cases for a zero-storage member?

- Do we allow taking the size of a FIC? If yes, what does that mean
w.r.t. taking the size of the same type name as a CIC member? (Is it
truly the same type name at that point? Or does implicit subclassing
happen?)


Feedback appreciated.

Thanks,

--
Matthew
inline-class.rst

Matthew Woehlke

unread,
Sep 2, 2015, 12:36:20 PM9/2/15
to std-pr...@isocpp.org
On 2015-09-02 12:13, Nicol Bolas wrote:
> On Wednesday, September 2, 2015 at 11:18:18 AM UTC-4, Matthew Woehlke wrote:
>> I guess what you are suggesting is that the size of a FIC should be the
>> size of 'class {}'? (Should we explicitly say that a CIC member written
>> as a FIC type is actually a different type? I.e. in the above,
>> decltype(Dog::foo) != decltype(Foo)? If implemented as implicit
>> subclassing, I think this would be okay, since the sizeof an instance
>> already can change depending on if the visible type is a subclass or
>> base class. in a sense we would be codifying EBO for this case.)
>
> Yes, that's the whole idea; you simply apply EBO logic to members.
>
> It's also why I would prefer that the empty optimization be split from the
> rest of the CIC restrictions.
>
> Just think about FICs for a moment. The only *necessary* difference between
> an FIC and a regular empty class is that FICs don't take up space in the
> class. All other restrictions are only *needed* for CICs.
>
> Think about it. Is there something functionally wrong or difficult to
> implement if you declaring a stack variable of an FIC type? No; it's no
> different from declaring a stack variable of any empty class type. After
> all, the `this` pointer for FICs doesn't get any of the special logic that
> CICs get. So why create that restriction? Same goes for any other use of
> FICs that your current proposal restricts, relative to other types.

Okay. Let's work through the implications of this... a CIC of a
freestanding empty class type has a 'this' bitwise equal to its CCC
'this'. This means that any CIC's are implicitly the first members in a
class, storage-wise, and additionally don't take up storage, so a) may
"overlap" with other CIC members, and b) don't affect the offset of
other non-inline-class members.

Well, okay, IIRC there is no requirement on how the compiler orders
class members anyway, so that part should be no issue. And we've already
discussed the point of multiple members having the same address.

So... I guess I don't see a problem looking at it that way. Do you? Does
anyone else?

> If you have FICs as simply empty classes, with all of the rights and
> privileges of *any* type, you can still put whatever restrictions you need
> on CICs. But those restrictions need not apply to FICs.
>
> At which point, it becomes a bit difficult to justify calling them all
> `inline classes`. There are two separate categories of thing: empty classes
> and nested inline classes.

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'? 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.

--
Matthew

Nicol Bolas

unread,
Sep 2, 2015, 1:09:05 PM9/2/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Wednesday, September 2, 2015 at 12:36:20 PM UTC-4, Matthew Woehlke wrote:
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".)

I think the empty decoration (whatever that may be) would work better on the declaration/definition for the class rather than the use of the class. That way, if I pass a truly empty class to a template, it doesn't have to put `inline` in its member definition to get the space optimization.

It should be a property of the type, not its particular use.


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'?

Syntactically, that requires look-ahead for the compiler to make sense of it. Contextual keywords are identifiers except in the specific context that accepts them. And in the two cases of contextual keywords, the context comes first.

So using contextual keywords, it would probably be best to do it like `final` (since the grammar already has space for it):

class outer
{
 
class nest inner
 
{
 
};
};

However, that posts a syntactical issue for nameless inner classes:

class outer
{
 
class inner //which is it?
 
{
 
};
};

Syntactically, `inline class` probably works with the fewest issues.

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.

Matthew Woehlke

unread,
Sep 2, 2015, 2:23:22 PM9/2/15
to std-pr...@isocpp.org
On 2015-09-02 13:09, Nicol Bolas wrote:
> I think the empty decoration (whatever that may be) would work better on
> the declaration/definition for the class rather than the use of the class.

I have mixed feelings there. Mostly, I think, because on a non-CIC
member FIC, the keyword would be superfluous in said context. Placing it
on the member declaration doesn't have this problem. OTOH...

> That way, if I pass a truly empty class to a template, it doesn't have to
> put `inline` in its member definition to get the space optimization.

...this is also a good point.

> On Wednesday, September 2, 2015 at 12:36:20 PM UTC-4, Matthew Woehlke wrote:
>> 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'?
>
> Syntactically, that requires look-ahead for the compiler to make sense of
> it. Contextual keywords are identifiers except in the specific context that
> accepts them. And in the two cases of contextual keywords, the context
> comes first.
>
> So using contextual keywords, it would probably be best to do it like
> `final` (since the grammar already has space for it):
>
> class outer
> {
> class nest inner
> {
> };
> };
>
> However, that posts a syntactical issue for nameless inner classes:
>
> class outer
> {
> class inner //which is it?
> {
> };
> };

Off the cuff... would this be a too-ridiculous solution for inner classes?

class outer
{
class inner : protected this
{
}
}

...or this?

class outer
{
class inner : extern outer
{
}
}

(Using 'extern' to disambiguate that we mean an outer class; 'extern'
could either imply 'protected', or we could use the usual default and
permit an explicit access specifier in addition. For that matter, the
class name could be omitted.)

...or we could combine the two and use 'extern this'. I think maybe I
like that better; it could be seen as "my 'this' is external to my
definition", which is roughly one way of thinking about an inner class.

>> 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.)

--
Matthew

Nicol Bolas

unread,
Sep 3, 2015, 10:45:33 AM9/3/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

That could work, since `extern` is a keyword. I'm not sure about using the colon though, since that suggests derived classes. It might be better to just put the `extern` immediately after the name (if there is a name). So it would be `class extern this` for anonymous classes.

>> 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.

Actually, the standard does not have to specific explicitly how the compiler deals with it. Just as the standard does not say that classes with virtual members must have a hidden vtable pointer. The standard allows for such an implementation by declaring that types with virtuals cannot be standard layout types and so forth. That gives the implementation the freedom to add hidden members and play games in the constructor.

The standard can do something similar here. It can require that inner classes still be trivially copyable (assuming that the inner classes don't do something that prevents trivial copyability). This prevents the compiler from using the "hidden `this` pointer" option for inner classes that have members. The only option this leaves is storing, for each inner class member, a byte-offset from the inner class member to the owning object.

This is fixed for each member, but it has to be stored in each member, since you might get a reference/pointer to an inner class member. The data can still be memcpy'd, so the type is still trivially copyable.

But the standard doesn't have to spell out the specific implementation. Just the behavior: maintain trivial copyability, but not standard layout.

This should all be implementable, because the compiler knows which inner class types are empty and which ones are not. So the compiler generates different code for the two cases. For empty inner class members, there is no need for an offset (since the pointer value to an empty class member is always that of the owning object). And for non-empty inner class members, the offset only breaks standard layout, not trivial copyability.
 
(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.)

Arrays of inner classes are more or less fine, since I'm pretty sure the compiler can do what is necessary to compute the offsets for each member.. The problem is arrays of empty classes (inner or not). In C++, you basically have to guarantee that this works, for any type:

T arr[5];
T
*pArr = arr;
pArr
+ 3 == &arr[3];

That would require that empty classes put into arrays must take up storage. The problem then is doing the pointer fixup needed to make the `this` pointer point to the right memory location. So you can't guarantee that each empty class instance gets the right pointer.

So if you have a member array of empty types, it must take up room. However, since you used an empty type as a member variable, you clearly don't want or expect it to take up room. Therefore, member arrays of empty types (again, inner or not) should simply be forbidden. You can still allocate arrays of empty types on the stack or heap, but those are always expected to take up memory.

I don't like this solution, but I don't have a better solution for it either.

Matthew Woehlke

unread,
Sep 3, 2015, 11:18:58 AM9/3/15
to std-pr...@isocpp.org
On 2015-09-03 10:45, Nicol Bolas wrote:
> On Wednesday, September 2, 2015 at 2:23:22 PM UTC-4, Matthew Woehlke wrote:
>> Off the cuff... would this be a too-ridiculous solution for inner classes?
>>
>> class outer
>> {
>> class inner : protected this
>> {
>> }
>> }
>>
>> ...or this?
>>
>> class outer
>> {
>> class inner : extern outer
>> {
>> }
>> }
>>
>> (Using 'extern' to disambiguate that we mean an outer class; 'extern'
>> could either imply 'protected', or we could use the usual default and
>> permit an explicit access specifier in addition. For that matter, the
>> class name could be omitted.)
>>
>> ...or we could combine the two and use 'extern this'. I think maybe I
>> like that better; it could be seen as "my 'this' is external to my
>> definition", which is roughly one way of thinking about an inner class.
>
> That could work, since `extern` is a keyword. I'm not sure about using the
> colon though, since that suggests derived classes. It might be better to
> just put the `extern` immediately after the name (if there is a name). So
> it would be `class extern this` for anonymous classes.

Okay. For now I'm going to keep the colon; partly because the token
chain seems a little odd to me otherwise, but also because inner classes
*act* like derived classes, at least in terms of access to parent members.

I still consider this a bikeshed, however, i.e. the syntax portion is a
weak proposal that I would be very open to improving.

>> 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.
>
> Actually, the standard does not have to specific explicitly *how* the
> compiler deals with it.

I'm not convinced that is true in this case. If we specify trivial
relocatability (and I think it would be a travesty to do otherwise), we
implicitly preclude storing the complete pointer (vs. an offset). Also,
consider:

struct Outer
{
struct Inner : extern this { ... };
Inner a, b, c[5];
};

I feel like we ought to be consistent across compilers as to whether or
not the above (particularly the array member) is well formed, and
whether decltype(a) == decltype(b). By specifying those (either way), we
effectively *have* specified the implementation, even if not explicitly.
Therefore, I think it's important to consider the benefits and drawbacks
of each possible implementation in order to decide how to answer those
questions.

> The only option this leaves is storing, for each inner class
> member, a byte-offset from the inner class member to the owning object.

Not true; the compiler *could* generate different code for each member
(I think this was Miro's suggestion originally). This would eliminate
the additional storage and a level of indirection, but would preclude
arrays of inner classes and would make the actual type be different per
member (and likely untypable¹). I personally am inclined away from that
approach, but as above, my strongest feeling is that we should have
*some* decision on this point.

(¹ I don't *think* there has been confusion here, but just to make
certain, what I mean by "untypable" is that the user cannot press
keyboard keys ("type") to form the type name, and must use decltype /
type deduction. That is, "type" as in "press keys on a keyboard", not
"type" as in "(internally) has a type name". Stupid ambiguous English
language ;-).)

> Arrays of inner classes are more or less fine, since I'm pretty sure the
> compiler can do what is necessary to compute the offsets for each member..

Yes, I would also think so...

> The problem is arrays of *empty* classes (inner or not). In C++, you
> basically have to guarantee that this works, for any type:
>
> T arr[5];
> T *pArr = arr;
> pArr + 3 == &arr[3];

Good point. OTOH, I can't figure out any way in which an array of empty
entities would be useful... they are all identical, so what is the point
of having more than one of them?

(Of course, if sizeof(empty_type) == 0, this would still work ;-), but I
know you don't want to go there, and I'm not going to suggest that we do.)

--
Matthew

Nicol Bolas

unread,
Sep 3, 2015, 3:21:15 PM9/3/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
There may be a bit of a misunderstanding on what I was trying to say.

I'm not saying that discussing the implementation is unimportant. What I means is that the feature needs to start with behavior. That is, what we want it to do. Then, we start to look at what implementing that behavior would require from compiler vendors. And in some cases, if it would be possible at all.

For example, Miro's suggestion is not merely implementation; it is behavior (as well). It says that inner classes are all templates, with an implicit CRTP use. So each inner class member is a separate type. Such behavior is apparent and it has consequences for how the feature could be used.

For example, if you want a function that takes a reference to an inner class member, you can't do that under his proposal without making this a template function. Whereas if it were just a concrete class, you could. Is this behavior we really want?

I don't think so.

By contrast, the question of pointer vs. offset is purely implementation. So long as the requested behavior (inner classes with state can maintain trivial copyability) has some implementation, and this implementation is not burdensome on compilers or at runtime, then the question of the implementation is more or less moot.

So I would say that after we iron out what behavior we want, then we can look to questions of how it gets implemented, or whether it is implementable at all. And we can talk about implementations to make sure that obvious problems can be worked around (which is why I don't suggest that inner classes with members be standard layout). But we shouldn't restrict behavior based on implementations unless it is actually unimplementable.

For example, I would say that inner classes should be as usable as regular classes except where that use would break the concept. Allocating an inner class on the stack or heap directly makes no sense, nor does trying to use an inner class from type A inside a class of type B (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?). Placement new also does not make sense on inner classes (though obviously you can use it on non-inner classes that contain inner class members).

Inner classes ought to be able to have constructors, and the owning class should be able to pass parameters to them. The fact that the compiler will generate some extra code for those constructors is more or less irrelevant. Inner classes should be able to have copy/move constructors if the user so desires.

Similarly, NSDM's of inner class types should be as functional as any variable of any class type, except where that functionality does not make sense. So getting pointers/references should work, and they should be just as functional as they would be for a regular class type. 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.

You might have some template code where the implementation creates a struct that contains an array of some type.

But this is also a good reason why it's OK to forbid it: because it's ultimately not very useful even if it were allowed ;)

That being said, we would also need some `std::is_inner_type` to detect when something is an inner class or not.

Matthew Woehlke

unread,
Sep 3, 2015, 5:11:00 PM9/3/15
to std-pr...@isocpp.org
On 2015-09-03 15:21, Nicol Bolas wrote:
> There may be a bit of a misunderstanding on what I was trying to say.

Maybe, maybe not... my point was merely that we need to discuss the
matter (whether starting from behavior or from implementation). I wasn't
getting a clear sense of your feelings in that respect from the previous
post. Whereas...

> For example, Miro's suggestion is not merely implementation; it is
> *behavior* (as well). It says that inner classes are all templates, with an
> implicit CRTP use. So each inner class member is a separate type. Such
> behavior is apparent and it has consequences for how the feature could be
> used.
>
> For example, if you want a function that takes a reference to an inner
> class member, you can't do that under his proposal without making this a
> template function. Whereas if it were just a concrete class, you could. Is
> this behavior we really want?
>
> I don't think so.

...this is much clearer; thanks :-).

I also tend to agree.

I think we agree that, behaviorally, an inner class should be trivially
copyable, and that all members of a particular inner class type should
actually be of that type, i.e. no hidden/implicit/magic templates.


I think I would also refrain from encouraging any implicit optimization
in the case of non-inline inner classes. The trouble here is that doing
so means that later adding a member (which would newly prevent the
optimization) results in the generated code for its member functions
changing, and possibly the inner class type's size/layout as well. I'd
be concerned that that makes it too easy to break compatibility when
changing code.

The case with an inline inner class is different, since the request for
the optimization is explict (via making the inner class inline).
Changing an inline class to non-inline or vice versa is a change that
much more obviously may affect BC/ABI.

> By contrast, the question of pointer vs. offset is purely implementation.
> So long as the requested behavior (inner classes with state can maintain
> trivial copyability) has *some* implementation, and this implementation is
> not burdensome on compilers or at runtime, then the question of the
> implementation is more or less moot.

Sure. I don't think a full-pointer implementation is *possible* with the
relocatable constraint, but I agree that the standardese can make that
an implicit (due to what is possible within the behavioral constraints)
rather than explicit requirement.

> For example, I would say that inner classes should be as usable as regular
> classes except where that use would break the concept.

Sure (and for inline classes also).

> Allocating an inner class on the stack or heap directly makes no
> sense, nor does trying to use an inner class from type A inside a
> class of type B

Right.

> (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.

Loosely related: B should be able to define an inner class type that
subclasses an inner class type of A.

> Placement new also does not make sense on inner classes (though
> obviously you can use it on non-inner classes that contain inner
> class members).

Agreed.

> Inner classes ought to be able to have constructors, and the owning class
> should be able to pass parameters to them. The fact that the compiler will
> generate some extra code for those constructors is more or less irrelevant.

Agreed.

> Inner classes should be able to have copy/move constructors if the user so
> desires.

Hmm... okay. Agreed.

> Similarly, NSDM's of inner class types should be as functional as any
> variable of any class type, except where that functionality does not make
> sense. So getting pointers/references should work, and they should be just
> as functional as they would be for a regular class type.

Agreed. Basically, an inner class is a regular class with
compiler-generated magic to tie it to its parent. (Basically, nothing we
can't do today, just more convenient.)

> 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.

> On Thursday, September 3, 2015 at 11:18:58 AM UTC-4, Matthew Woehlke wrote:
>> I can't figure out any way in which an array of empty entities
>> would be useful... they are all identical, so what is the point of
>> having more than one of them?
>
> You might have some template code where the implementation creates a struct
> that contains an array of some type.
>
> But this is also a good reason why it's OK to forbid it: because it's
> ultimately not very useful even if it were allowed ;)

Right :-). Also, this is very much a corner case; you have some
template, and a type that is an inner class that is *also* an inline
class. In fact, I'm not even sure you can arrange such an instance
except that it would *always* be broken.

> That being said, we would also need some `std::is_inner_type` to detect
> when something is an inner class or not.

True, and good point. I had that for inline classes; it makes sense for
splitting the concepts to have a trait for both.

--
Matthew

Nicol Bolas

unread,
Sep 3, 2015, 10:44:20 PM9/3/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

If you add a new (non-empty) member to a class, you've already made the choice to break compatibility. Not unless you remove some other member to compensate. Which obviously you can't do if the class had no members to begin with.

I guess the biggest concern is that, with empty inner classes, the containing class remains standard layout, which is obviously lost if you make the inner class non-empty. It's unfortunate, but I don't think there's really anything to be done for it.

Generally speaking, I think the use cases for empty inner classes and non-empty ones are sufficiently different that the choice will typically be made when the class's interface is being designed.

> (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.

I think it could work for both cases. Consider the case for non-virtual inheritance.

Remember that the standard does not require compilers to put members of a derived class after the members of base classes in memory. So when you access a regular member of A through an object of type B, the compiler is allowed to do some pointer fixup to convert the `this` pointer into a pointer to A.

However, the inner class member is actually a member of B, not A. So what does the compiler do?

If the inner class is not empty, then it will have a static offset from itself (which resides in B) to the owning object (A). This will always work, since the distance from the object to A will be fixed at compile time. The offset may be negative on some compilers, but the mechanism works nevertheless.

For empty inner classes, something different must happen. Since it is an inner class of A, the compiler will have to convert the pointer to B into a pointer to A, then use that as the `this` pointer. And that should be something the compiler can easily do, since it does so already when accessing members of A through an object of type B. It's just a static_cast<A*>.

But if the compiler can do both of those... what prevents the compiler from using these same tools in the case of virtual inheritance? Now, I admit that I have little experience with how virtual inheritance gets implemented. So I may be missing something critical.

I think the derived class is still laid out contiguously even in the case of virtual inheritance. Therefore, the offset from a particular member to the pointer to the virtual base is a static offset. And it's an offset that (I think) does not change depending on other derived classes and so forth. If that's true, the offset can be baked into the object instance.

Egh, the more I think about it, the less sure I am that a static offset will work. Or at least, not a offset defined solely by the definition of class B. Maybe someone who actually knows about this stuff can comment.

For empty inner classes, it simply does whatever a static_cast<Base*> would do before calling the member. It may require some added pointer work, fetching memory locations and such. But it ought to still work.

Then again, it's virtual inheritance; if we have to lose it, no big loss ;)

> 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.

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. 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).

And no fair calling destructors explicitly either ;)

Matthew Woehlke

unread,
Sep 4, 2015, 10:08:48 AM9/4/15
to std-pr...@isocpp.org
On 2015-09-03 22:44, Nicol Bolas wrote:
> On Thursday, September 3, 2015 at 5:11:00 PM UTC-4, Matthew Woehlke wrote:
>> I think I would also refrain from encouraging any implicit optimization
>> in the case of non-inline inner classes. The trouble here is that doing
>> so means that later adding a member (which would newly prevent the
>> optimization) results in the generated code for its member functions
>> changing, and possibly the inner class type's size/layout as well. I'd
>> be concerned that that makes it too easy to break compatibility when
>> changing code.
>
> If you add a new (non-empty) member to a class, you've already made the
> choice to break compatibility. Not unless you remove some other member to
> compensate. Which obviously you can't do if the class had no members to
> begin with.

I'm thinking more of the case of a unique inner class member, for which
the compiler could optimize away the dynamic offset into a constant
offset. This would result in a size change of the *inner* class, which
may be unexpected; not just the expected change to the outer class. But
now that you mention it, I'm having a harder time convincing myself
that's a genuine issue.

Anyway, I'm not saying that I would *forbid* that, just not say anything
to *encourage* vendors to implement such an optimization. If vendors
wish to do so anyway, let the consequences be on their own heads :-).

> I guess the biggest concern is that, with empty inner classes, the
> containing class remains standard layout, which is obviously lost if you
> make the inner class non-empty. It's unfortunate, but I don't think there's
> really anything to be done for it.

Again, I would not mandate that a non-inline empty inner class has zero
size. Rather the opposite, if anything; I would be inclined that it has
a minimum size == sizeof(ptrdiff_t).

On a related note, is the size of a non-inline inner class equal to the
size of its members (plus usual padding) plus an additional ptrdiff_t?
(That is, *not* equal to the outer class size?)

> Generally speaking, I think the use cases for empty inner classes and
> non-empty ones are sufficiently different that the choice will typically be
> made when the class's interface is being designed.

Right. That is, what I said above; non-inline empty inner classes should
not specify any different behavior from non-empty inner classes. If you
change an inner class from inline to non-inline (or vice versa), that's
something that much more obviously will have ABI implications.

>> (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.
>
> I think it could work for both cases. Consider the case for
> non-virtual inheritance. [...] the inner class member is actually a
> member of B, not A. So what does the compiler do?
>
> If the inner class is not empty, then it will have a static offset from
> itself (which resides in B) to the owning object (A). This will always
> work, since the distance from the object to A will be fixed at compile
> time. The offset may be negative on some compilers, but the mechanism works
> nevertheless.

Right; exactly as I would expect.

> For empty inner classes, something different must happen. Since it is an
> inner class of A, the compiler will have to convert the pointer to B into a
> pointer to A, then use that as the `this` pointer. And that should be
> something the compiler can easily do, since it does so already when
> accessing members of A through an object of type B. It's just a
> static_cast<A*>.

Right.

> But if the compiler can do both of those... what prevents the compiler from
> using these same tools in the case of virtual inheritance? Now, I admit
> that I have little experience with how virtual inheritance gets
> implemented. So I may be missing something critical.
>
> I think the derived class is still laid out contiguously even in the case
> of virtual inheritance.

Um... for the *final* class, yes. Hmm...

Maybe this does work, but it's just complicated. Assume that the storage
is contiguous (i.e. it is still rational to use offsets). The issue
arises in that, let's say we have class D derived from B1 and B2, both
having virtual base A, and B1 having an inner class member of type A::I.

Supposedly then, D knows the static offset from B1's inner class to A.
However, the offset needs to be initialized by B1, which does *not* know
where A lives relative to it. Presumably upon construction, however, B1
already knows where it's A lives. So it could compute the offset at that
point. The difference is that the offset must be computed by B1's ctor
based on the actual offset to A, rather than being constant.

I think that can work... it just means that the offset isn't a
compile-time constant, because it depends on what derived class is
actually instantiated.

Okay, I think we agree to not forbid it a priori and wait to see if
implementation is possible.

> For empty inner classes, it simply does whatever a static_cast<Base*> would
> do before calling the member. It may require some added pointer work,
> fetching memory locations and such. But it ought to still work.

Right; no problem for this case.

>> 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.

> 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. In any *other* context, however,
that should be illegal. IOW:

void foo(Outer::Inner&); // okay
void foo(Outer::Inner); // error

The problem of course is that this would require instantiating an inner
class outside of its outer class.

Passing an inner class as an rvalue-reference is a little bit trickier,
since this should ideally be allowed:

Outer outer;
bar(std::move(outer).a);

(May require lifetime extension of 'outer'...)

I'm starting to think that the only thing that should be explicitly
specified is to forbid instantiation outside of the outer class, and let
any restrictions on copy/move flow out of that.

> And no fair calling destructors explicitly either ;)

I guess this would be the flip side of placement new... so, yeah, makes
sense.

--
Matthew

Nicol Bolas

unread,
Sep 4, 2015, 12:51:10 PM9/4/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Friday, September 4, 2015 at 10:08:48 AM UTC-4, Matthew Woehlke wrote:
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.

Here's the problem. Remember what the actual inner class looks like:

<<inducer>> class Inner
{
...
int32_t __offset;
};

Regardless of the specific information, each class member has its own distinct offset, which goes from that class member's `this` to the owning class's `this`.

So `out.a.__offset` and `out.b.__offset` are different values.

However, if Inner is actually trivially copyable, then that means you have to be able to copy it via memcpy. The trivial copy constructor/assignment can also be a memcpy.

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.

I don't think there's a way to get around that without expressly forbidding the copying/moving of inner class members. It's a weird thing; the actual inner class is non-copyable at all, but the class that contains it as a member is copyable (and trivially so where applicable).

Ugh, that's going to be a painful thing to standardize.
 

> 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.

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. But for stateful inner classes, one of their members is a value that is intrinsic to that specific member. So memcpying it is non-functional. Therefore, the copy constructor cannot be trivial.

The only way I can see to avoid that is to disallow explicit copy constructor invocation. Then, the compiler can just do a bit of magic for implicit copy constructor use.

However, I realize that if you disallow this, then the owning class can't have a explicit copy constructor that copies stateful inner class members. So the owning class is forced to rely on implicit copy/move constructors only. That's also bad.

I don't like where this is going...

Matthew Woehlke

unread,
Sep 4, 2015, 1:51:56 PM9/4/15
to std-pr...@isocpp.org
I think that the offset should/would not be accessible to the user, but
always managed by the containing class.

Rather than thinking of the offset as a data member, it might be helpful
to think of it as something like the extra information that a heap
allocator may place before/after a heap allocation (e.g. the size of the
chunk). This data is (considered as) not user accessible and is a
function of the allocation, not the data contained in the chunk.
Similarly, an inner class's offset is a function of its instantiation by
a containing class is is not "part" of the inner class for the purpose
of copying the inner class (although it is counted in sizeof()).

This does mean that, conceptually, a default copy ctor for an outer
class looks like:

- call base class ctor
- set offsets in inner class members
- call copy ctors for all class members (inner classes or otherwise)

Of course, the compiler can know that the offsets of the inner class
members of the source and target are the same, and therefore if the type
is otherwise relocatably (i.e. copy by memcpy/memmove), then the offsets
are also, and so the whole thing can be one big memcpy/memmove.

(Or are they? I'm not sure in the case of virtual inheritance if the
source and target aren't actually the same type, but then, in such case
you're probably having to fix up the base pointers anyway, so already
you lost relocatability. IOW I still don't see a problem. In fact, I
wonder if virtual inheritance hasn't already had to address these issues...)

> 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.

>> You definitely *should* be able to copy/move construct them in the
>> context of the outer class's ctors.

Specifically, I mean this had better work, or we have a real mess:

Outer::Outer(Outer const& other) : inner(other.inner)
{
...
}

Having a non-default copy ctor for the outer class require that the user
write out how to copy the inner class members would be... as you said:

> It's a weird thing; the actual inner class is non-copyable at all,
> but the class that contains it as a member is copyable (and trivially
> so where applicable).

...or in my own word, "atrocious" :-). And...

> Ugh, that's going to be a painful thing to standardize.

...I would go so far as to say that more strongly; not merely "painful",
but likely "guaranteed to kill the proposal".

So I think we agree to not go there. Fortunately, I don't think we need to.

> 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...

--
Matthew

Nicol Bolas

unread,
Sep 4, 2015, 2:38:52 PM9/4/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

That's why I used the double-underscore on the member name. It's there for exposition, but is an implementation detail that's not accessible by the user.

My point is that, user-accessible or not, it is part of `Inner`'s storage. And there's no other way to implement it. After all, code may only have a pointer/reference to the `Inner`. Therefore, each `Inner` instance must have sufficient data to compute the location of its containing `Outer` object from just its own `this` pointer.

So putting those offsets in the owning class somewhere won't work.
 
> 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 anymore.
 
> 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...

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.

Nicol Bolas

unread,
Sep 4, 2015, 2:51:57 PM9/4/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Wait, I think I understand sort of what you were getting at. It still won't work, but for a different reason.

You were basically saying that inner class implementations would assume that the four bytes preceeding its `this` pointer would contain the offset. And that it's up to how the individual owning class(es) lay those objects out to set those values. So you can copy individual members just fine, since the offset isn't strictly part of `this`. And an instance can simply decrement `this` four bytes and get the offset, since instances of inner classes can only be created within another class.

The problem then is with arrays of inner classes. Earlier, we forbade arrays of empty classes because they don't make sense and would have to take up space. But arrays of stateful inner classes couldn't work with this either. The reason being that arrays have to be contiguous, that the offset from one array entry to another must be `sizeof(T)`. So there's no room available for an offset.

Not unless that offset is part of `sizeof(T)`...

So either way, the offset has to be a part of the type.

Nicol Bolas

unread,
Sep 4, 2015, 2:53:02 PM9/4/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Of course, you could fix this by forbidding arrays of inner classes altogether ;)

John Yates

unread,
Sep 4, 2015, 3:40:38 PM9/4/15
to std-pr...@isocpp.org, mwoehlk...@gmail.com
On Fri, Sep 4, 2015 at 2:53 PM, Nicol Bolas <jmck...@gmail.com> wrote:
Of course, you could fix this by forbidding arrays of inner classes altogether ;)

Why the smiley?

I would happily live with that restriction.  It is not like a lack of arrays of inner classes renders unimplementable something that I otherwise might have been able to express.  It just means that in this obscure corner of implementation I have to fallback on pre-inner-class methodologies.  So what?

/john
 

Miro Knejp

unread,
Sep 4, 2015, 3:59:41 PM9/4/15
to std-pr...@isocpp.org
Am 04.09.2015 um 20:38 schrieb Nicol Bolas:

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.
The let's try to specify it.
The problem here is, that the nested instance itself (if it has an offset) is not trivially copyable, but it doesn't necessarily prevent the parent fom being. Further, an offset member is only necessary if the nested class accesses non-static members of the parent.

So let me try:
* An empty nested class is TriviallyCopyable under the same rules as if it were a regular class (assuming empty inline classes all have the same "this" pointer as the parent and thus cannot form arrays).
* A non-empty nested class is not TriviallyCopyable if any of its non-static members access non-static members of the parent. This can only be determined if all member functions are defined inline, otherwise the compiler has to assume it needs an offset.
* If a nested class would fulfill all requirements for TriviallyCopyable if it were a regular class, it doesn't prevent the parent class from being TriviallyCopyable.

However, I have to point out that the offset-based approach isn't what I'd call "zero overhead", neither in storage nor runtime. This can be implemented alredy by storing the this-offset instead of a this-pointer to the parent and doing the pointer-arithmetic yourself with the appropriate amount of casting. It's not pretty but it works. I'd like to see a way to do *true* zero-overhead inline classes, as that is currently not possible (except for weird union hacks with questionable portability), hence my approach involved type manipulation. I understand the opposition, even though I don't necessarily agree with all of it and am still looking for an easier way.

Matthew Woehlke

unread,
Sep 4, 2015, 4:40:33 PM9/4/15
to std-pr...@isocpp.org
On 2015-09-04 16:01, Miro Knejp wrote:
> Further, an offset member is only necessary if the nested class
> accesses non-static members of the parent.

Wrong. The compiler must know the size of the class from *class*
definition (e.g. foo.h), which may not contain definitions of the class
*members* (e.g foo.cpp). Therefore, the compiler *must* assume that such
access occurs.

Even if all methods are inline, the notion that changing the code in a
member function can change the size of a type... just... no.

> * An empty nested class is TriviallyCopyable under the same rules as if
> it were a regular class (assuming empty inline classes all have the same
> "this" pointer as the parent and thus cannot form arrays).

An empty (inline) class (inner or not) cannot be constructed or
destroyed, period, and is irrelevant to whether any object having one as
a member is trivially copyable.

--
Matthew

Matthew Woehlke

unread,
Sep 4, 2015, 4:45:09 PM9/4/15
to std-pr...@isocpp.org
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. However, how would you ever even get into a situation
where non-user-written code would be trying to do that? (Remember,
relocatability usually comes into play in dynamic arrays, but you can't
have a dynamic array of inner classes...)

--
Matthew

Miro Knejp

unread,
Sep 4, 2015, 4:55:40 PM9/4/15
to std-pr...@isocpp.org
Am 04.09.2015 um 22:40 schrieb Matthew Woehlke:
> On 2015-09-04 16:01, Miro Knejp wrote:
>> Further, an offset member is only necessary if the nested class
>> accesses non-static members of the parent.
> Wrong. The compiler must know the size of the class from *class*
> definition (e.g. foo.h), which may not contain definitions of the class
> *members* (e.g foo.cpp). Therefore, the compiler *must* assume that such
> access occurs.
Seriously? Just two lines further down I even mention that "This can
only be determined if all member functions are defined inline, otherwise
the compiler has to assume it needs an offset."
>
> Even if all methods are inline, the notion that changing the code in a
> member function can change the size of a type... just... no.
Probably true. If you never access the parent you can just as well use a
regular subobject.
>
>> * An empty nested class is TriviallyCopyable under the same rules as if
>> it were a regular class (assuming empty inline classes all have the same
>> "this" pointer as the parent and thus cannot form arrays).
> An empty (inline) class (inner or not) cannot be constructed or
> destroyed, period, and is irrelevant to whether any object having one as
> a member is trivially copyable.
>
Why not? If you call it a class it should support user-defined special
member functions. Just because it doesn't have non-static state doesn't
mean it cannot have construction/destruction/copy side effects. Sounds
like an arbitrary restriction to me.

Nicol Bolas

unread,
Sep 5, 2015, 9:23:20 AM9/5/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Friday, September 4, 2015 at 4:45:09 PM UTC-4, Matthew Woehlke wrote:
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.

Yes, and vtable pointers are also an implementation detail, one that is "not a member for the purpose of standardese". And vtable pointers are exactly why virtual functions/base classes break trivial copyability. It allows implementations the freedom to insert those implementation details into the class itself.

The same seems to be true here.
 
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.

Trivial copying is trivial copying; by definition, it cannot include specialized logic to exclude certain bits. That sort of thing would make it non-trivial. Again, this is why virtual classes aren't trivially copyable: because the compiler has to insert specialized code to prevent modifying any implementation details during the copy. And thus, the copy cannot be trivial.

If the copy constructor has to be anything other than `memcpy(dest, src, sizeof(T))`, then it's non-trivial.

So only implementation where trivial copying between different members of the same inner class type would work is if the inner class itself does not contain the offset. Which returns to the aforementioned problem: how do you convert a pointer to the inner class member into a pointer to the owning instance, if the only information you have is the pointer to the inner class member?

Laying out the owning class such that it naturally stores an offset to non-empty inner classes is a functional implementation, but it also means you can't have arrays of inner classes.

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.

It doesn't matter how many are being copied, one or twenty. If it can't be copied with a memcpy, it's not trivially copyable.

Nicol Bolas

unread,
Sep 6, 2015, 7:04:12 PM9/6/15
to ISO C++ Standard - Future Proposals
OK, given the various discussions on this thread, I've put together an alternate look at this, one which separates empty types from inner classes and addresses most of the questions around stateful inner classes. I hope that it spells out all of the various interactions of inner classes, to make sure that there is resolution on how they work. I discovered a few holes while I was describing behavior (constructors and whether they can access owned object internals), so I tried to plug them as best I could.

I also found an interesting question with regard to stateless classes and aggregate initialization. I'm not really sure what the answer is or should be.
Stateless And Inner Classes.html

Matthew Woehlke

unread,
Sep 7, 2015, 10:13:20 AM9/7/15
to std-pr...@isocpp.org
On 2015-09-04 16:57, Miro Knejp wrote:
> Am 04.09.2015 um 22:40 schrieb Matthew Woehlke:
>> On 2015-09-04 16:01, Miro Knejp wrote:
>>> Further, an offset member is only necessary if the nested class
>>> accesses non-static members of the parent.
>>
>> Wrong. The compiler must know the size of the class from *class*
>> definition (e.g. foo.h), which may not contain definitions of the class
>> *members* (e.g foo.cpp). Therefore, the compiler *must* assume that such
>> access occurs.
>
> Seriously? Just two lines further down I even mention that "This can
> only be determined if all member functions are defined inline, otherwise
> the compiler has to assume it needs an offset."

Okay, maybe I missed that. Apologies. Still...

>> Even if all methods are inline, the notion that changing the code in a
>> member function can change the size of a type... just... no.

I stand by this.

> Probably true. If you never access the parent you can just as well use a
> regular subobject.

Not sure how this relates?

>>> * An empty nested class is TriviallyCopyable under the same rules as if
>>> it were a regular class (assuming empty inline classes all have the same
>>> "this" pointer as the parent and thus cannot form arrays).
>>
>> An empty (inline) class (inner or not) cannot be constructed or
>> destroyed, period, and is irrelevant to whether any object having one as
>> a member is trivially copyable.
>
> Why not?

Because it occupies no storage. Hmm... the only possible use of
ctors/dtors would be for side effects. I suppose that *might* be useful,
but OTOH it's easier to simply state that such objects are irrelevant to
construction/destruction than work out the rules for the same.

Maybe that could be relaxed...

--
Matthew

Matthew Woehlke

unread,
Sep 7, 2015, 1:33:35 PM9/7/15
to std-pr...@isocpp.org
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"?

> 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? I want to say "yes" (or
possibly "implementation defined"), so that you can do things like take
its address.

> Question: Do we want stateless class NSDM's to take part in
> aggregate initialization? If we want stateless class members to
> effectively be transparent, to purely be implementation details, then
> we probably don't want them to be part of aggregate initialization.
> At the same time, stateless classes still exist and can still have
> constructors.

I could say "no, conditionally", but I think a better answer is to
provide ctors if you don't want them being mentioned in constructing the
containing class. (I.e. suppress aggregate initialization.) Ergo, my
inclination is to just say "yes".

> The inner class can access NSDM's

Nit: shouldn't that just be "members"? I don't think the intent was to
limit to data members? (And why limit to non-static?)

> 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?

> The inner class of a class is implicitly friends

Editorial: singular/plural mismatch ;-).

> 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;

> For multiple nestings of stateful inner classes, each level has its
> own offset to get to the next level.

Um... right. I hadn't thought about that. Good catch :-).

> 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.

Also, I think the "every other..." part is not technically true; *any*
construction needs to initialize the offsets; some may be able to do so
more trivially than others. (E.g. copy/move can just duplicate the
offset bits from the other instance, along with everything else.
Pedantically, it's still initializing them, however.)

> Trivial copyability is probably more generally useful than the
> ability to aggregate stateful inner classes into arrays.

Agreed, but both would still be better :-).

> 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?

> woning

Editorial: typo (s.b. "owning") ;-).

Thanks again!

--
Matthew

Matthew Woehlke

unread,
Sep 7, 2015, 1:40:37 PM9/7/15
to std-pr...@isocpp.org
In the same member case (one or many elements), you're doing something like:

memcpy(dst, src, num_elems * sizeof(T));

In the different member, one element case, you're doing something like:

memcpy(fixup(dst), fixup(src), adjusted_size<T>());

Both cases are memcpy, though I suppose you are objecting to the need to
do some pointer/size adjustments in the second case.

Anyway, the third case I was referring to, which can never work, is:

memmove(&dst[j], &src[i], n * sizeof(T));

...that is, doing a "copy" (in this case I'm also considering the
possibility that src==dst, i.e. shuffling values within an array) of
multiple contiguous elements from one block to another, where the source
and destination blocks are different members (either entirely, or
different subsections of the same array member). That's the case that
*can't* work. The single case "can" (if you permit it to be adjusted to
exclude the offset bytes).

Hmm... it occurs to me to wonder, if we were to simply forbid arrays,
how many compilers would choose to support them anyway as an extension
and just say "don't to that" as far as the case where a user memcpy
would break things.

--
Matthew

Miro Knejp

unread,
Sep 7, 2015, 2:57:40 PM9/7/15
to std-pr...@isocpp.org
Am 07.09.2015 um 16:12 schrieb Matthew Woehlke:
>> Probably true. If you never access the parent you can just as well use a
>> regular subobject.
> Not sure how this relates?
I agreed with your statement.
>>>> * An empty nested class is TriviallyCopyable under the same rules as if
>>>> it were a regular class (assuming empty inline classes all have the same
>>>> "this" pointer as the parent and thus cannot form arrays).
>>> An empty (inline) class (inner or not) cannot be constructed or
>>> destroyed, period, and is irrelevant to whether any object having one as
>>> a member is trivially copyable.
>> Why not?
> Because it occupies no storage. Hmm... the only possible use of
> ctors/dtors would be for side effects. I suppose that *might* be useful,
> but OTOH it's easier to simply state that such objects are irrelevant to
> construction/destruction than work out the rules for the same.
>
> Maybe that could be relaxed...
I don't see the need for special rules. The order of ctor/dtor/copy
calls should simply match the order of regular subobjects, i.e. dictated
by member declaration order. The only difference is that empty nested
classes don't occupy storage, but they can still have a well-defined
lifetime like any other subobject.

Even the array restriction may not be necessary *if* (big if) sizeof(T)
gives 0 for empty nested classes, as that doesn't violate the pointer
arithmetic of arrays. As Nicol has written in his note, there are
already cases where unrelatd empty objects can have the same address,
the real novelty is if &x[0] == &x[0] + 1 holds. This may or may not
break assumptions existing (template) code has, but if they stick to
sizeof() and type traits, generic code *shouldn't* break. But this
definitely needs more research.

Nicol Bolas

unread,
Sep 7, 2015, 3:04:17 PM9/7/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Monday, September 7, 2015 at 1:33:35 PM UTC-4, Matthew Woehlke wrote:
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"?

It would be no different than this:

class empty {};
class derived1 : public empty
{
 
int foo;
};

class derived2 : public empty
{
 
float bar;
};

You can get pointers to both empty base class objects and try to copy them with `memcpy`. And it will fail in the exactly the same way.

Using memcpy on empty types, whether stateless or not, has problems. OK, to be fair, it only has problems currently if the empty type is a base class undergoing EBO.

> 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?

The reason to forbid variable arrays of stateless types is because stateless types are not supposed to have storage. Even when not declared as members of types (globals, automatic locals, etc), the compiler is allowed to not give them any particular storage.

Arrays have to have storage, so to resolve the contradiction, we don't allow stateless arrays.

However, if you allocate a stateless type with `new` (of any form), you are explicitly asking for something which has storage. Therefore, if you can use `new` on a stateless type, you ought to be able to use `new[]` on it as well.

Oh, and this made me think of another interesting question: does a
static member of stateless type have storage?

A static member of any type is simply a global member that has to be qualified by that type's name (and for member functions, gets free access to that type's privates). That's the only difference between a static member and a global declaration.

There's no reason for the fact that the declaration is in a stateless type (or for that matter, inner type) to affect this.

> 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?

You were the one who suggested that the user be allowed to invoke copy/move constructors/assignment themselves when I originally suggested that the user ought not be able to copy/move inner classes directly.

I see no reason to declare this to be undefined behavior. It should either be allowed or forbidden.
 
> 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).
 
> 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;

Actually, it also means you can write `c.a.a.a.a.a`.

And even if you forbid implicit conversions outside of the implementation of an inner class, you could still do that within the inner class. So there's no way around that one: if an inner class can reach the outer class members, then it can reach members of inner class type. Including members of its own type.

> 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". Arrays have certain expected behavior. And if something is going to "look" like an array, it had better behave like one. In every detail.

Otherwise, it shouldn't look like an array.
 
> 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.

I did:

-> Objects which have stateful inner class members cannot be trivial types.
 
> Inner classes can be used for properties.

I, of course, would also include my "as-a interface" example here.

But that's still just properties; you would implement the same thing in C#/Python/etc as properties. It's only zero overhead because your proxy objects take up no room.

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?
 
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?

Because the feature didn't do anything special with the CRTP. It still requires you to list the owning class as a template parameter explicitly. The totality of the feature are these:

1) Inner class templates designate one of their template arguments as being their owning class. And therefore, names that looked up through `this` are dependent names.

2) When you instantiate the template, the class instance is owned by that particular template parameter.

It doesn't change how the CRTP works, nor does it make it easier to use.

Matthew Woehlke

unread,
Sep 7, 2015, 4:08:58 PM9/7/15
to std-pr...@isocpp.org
On 2015-09-07 15:04, Nicol Bolas wrote:
> On Monday, September 7, 2015 at 1:33:35 PM UTC-4, Matthew Woehlke wrote:
>> On 2015-09-06 19:04, Nicol Bolas wrote:
>>> 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"?
>
> It would be no different than [example elided]
>
> Using memcpy on empty types, whether stateless or not, has problems. OK, to
> be fair, it only has problems currently if the empty type is a base class
> undergoing EBO.

Okay. Sounds like we agree it isn't a problem. I sort-of expected that,
but just wanted to point it out.

>>> 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?
>
> The reason to forbid variable arrays of stateless types is because
> stateless types are not supposed to have storage. Even when not declared as
> members of types (globals, automatic locals, etc), the compiler is allowed
> to not give them any particular storage.

So... related to:

>> 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 it
allowed to be null? (I take it this means that a naïve memcpy of a
stateless type is UB?)

I was assuming that a non-NSDM stateless type would simply have minimal
real (if pointless) storage, since it's likely not going to be a
performance issue for non-NSDM's (as there will necessarily be O(1) of
them) and it eliminates bunches of questions like the above. In which
case, the array prohibition seems silly.

If that's not the case, however, then it makes more sense.

>> 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?
>
> You were the one who suggested that the user be allowed to invoke copy/move
> constructors/assignment themselves when I originally suggested that the
> user ought not be able to copy/move inner classes directly.

...because I don't see how you're supposed to write an outer class user
defined copy ctor if you can't call the inner class members' copy ctors.

> 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. (Presumably we
would then provide a way to do the necessary adjustments, but discussing
that is moot unless we agree to the UB in the first place.)

>>> 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.

>>> 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;
>
> Actually, it also means you can write `c.a.a.a.a.a`.

(You'll notice I wasn't strictly alternating in that example ;-).)

> And even if you forbid implicit conversions outside of the implementation
> of an inner class, you could still do that *within* the inner class.

Yes and no. You couldn't 'this->a.b' if decltype(*this) != decltype(a),
because 'b' would be a protected member in that case. (At least it was;
my original suggestion was for access to the outer class to be
protected.) 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 example:

class Outer
{
private: <<inner>> class P { ... } _0, _1, _2, _3;
public: <<stateless>> <<inner>> class {
P& operator[](size_t) { ... }
} p;
};

(I was going to suggest something that would hold an array of
references, but I forgot those aren't legal ;-).)

Certainly you can hack something up by hand. The "something" mentioned
above would likely work similarly, but would move the boilerplate into a
library class. (Ideally it would be usable like std::array, i.e. a
template taking a typename and a count, that creates the actual members
internally rather than referring to external members.)

> Arrays have certain expected behavior. And if something is
> going to "look" like an array, it had better *behave *like one. In every
> detail.
>
> Otherwise, it shouldn't look like an array.

Probably you would not be able to do things like pass such an array as a
parameter (unless by reference to the aforementioned utility type).
Certainly you wouldn't have direct access to the block of bytes.

>>> 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.
>
> I did:
>
> -> Objects which have stateful inner class members cannot be trivial types.

Key phrase: "to this"; apparently I'd forgotten that by the time I got
to the above quoted text. (Actually... I'm not entirely sure what I was
thinking when I wrote the above...)

>>> Inner classes can be used for properties.
>>
>> I, of course, would also include my "as-a interface" example here.
>
> But that's still just properties; you would implement the same thing in
> C#/Python/etc as properties. It's only zero overhead because your proxy
> objects take up no room.

Not exactly; in that case, you probably don't have assignment /
conversion (or if you do, they look like modifying the whole class), and
they almost *certainly* have other members, which properties usually
would not.

Anyway, it's not important; at most it strengthens the rationale.

>> 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)

>>> 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?
>
> Because the feature didn't do anything special with the CRTP. It still
> requires you to list the owning class as a template parameter explicitly.
> The totality of the feature are these:
>
> 1) Inner class templates designate one of their template arguments as being
> their owning class. And therefore, names that looked up through `this` are
> dependent names.

This sounds a lot like CRTP, or at least one of the sorts of problems
that CRTP addresses.

> 2) When you instantiate the template, the class instance is owned by that
> particular template parameter.

Presumably this means new syntax.

Why not this instead:

A mixin² template is a class template that must be subclassed in order
to be used. All references to 'this' are deferred until the template is
actually instantiated. When you instantiate the mixin template, the
actual type of *the template* is implicitly converted to the derived
type, with 'this' names resolved accordingly.

Does that not solve the problem equally well? I think yes, *but* it is
useful to non-inner classes as well, which makes it far more broadly
useful (likely able to replace many existing uses of CRTP).

I don't object to making CRTP / CRTP-like things easier. I *do* object
to doing so for only a limited subset of use cases for no obvious reason.

(² Name subject to change; mainly, I'm calling it something other than
CRTP because derivation would not need to repeat the class name.)

--
Matthew

Nicol Bolas

unread,
Sep 9, 2015, 3:50:16 PM9/9/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

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.

It's up to the implementation. If an implementation wants to allow stateless non-NSDMs to take up no room, that's great; they have the freedom to do that. If they can't or don't want to, then that's fine too; give them the minimum size.
 
> 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.

A type cannot be both trivially copyable and have memcpy yield UB. So either stateful inner class members are trivially copyable (and thus memcpy is defined behavior) or they are not (and thus memcpy is undefined).

>>> 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').

What exactly would that mean? Are you saying that if you're an inner class, you can refer to any member of your owning class(es)... except other inner class members?

That's a really specific rule. I'm not sure why we need something like that.
 
>>> 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.

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.

>> 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)

You've done more to explain what he meant by "service object" than he did, and it's still not clear what that code would look like. That's why actual example code is a better example; I'm still not sure what you stream writer suggestion would look like.

Matthew Woehlke

unread,
Sep 9, 2015, 5:58:26 PM9/9/15
to std-pr...@isocpp.org
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?

>> 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').
>
> What exactly would that mean? Are you saying that if you're an inner class,
> you can refer to any member of your owning class(es)... except other inner
> class members?

No (if I'm understanding the above). I'm saying that "this" is special:

class Outer
{
<<inner>> class Inner { int x; void foo(); } a;
}

void Outer::Inner::foo()
{
this->x; // okay; our own member
this->a; // okay; access to parent members allowed via 'this'
this->a.a; // error; 'a' is not a member of I1
}

IOW, if a direct member isn't found, the compiler only looks on the
containing classes when the RHS of '->' (or '.') is "this". The effect
is that the "innerness" of a class is only usable within the bodies of
member functions of that class.

A less restrictive form is making lookup on the outer class act as if
the outer class is a protected base. This would prevent using a pointer
to a different class and/or outside a member function to go back out to
the outer class, but will still allow silliness within an inner class
member function w.r.t. members of the same inner class type.

>>>> 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 };

I probably should have clarified the original comment: by "something" I
mean a library class template. *NOT* syntax, and *ESPECIALLY NOT* a
perversion of a C-style array type.

The point is (merely) to be able to write, where it makes for a sensible
API:

outer->inner[<<expr>>].<<expr>>;

If one can also write:

o1->inner = o2->inner; // 'inner' is an "array"

...that would be a bonus, and seems like it ought to also be achievable
with the library type.

> You've done more to explain what he meant by "service object" than he did,
> and it's still not clear what that code would look like. That's why actual
> example code is a better example; I'm still not sure what you stream writer
> suggestion would look like.

Lame example:

class Server
{
int total_messages = 0;
<<inner>> class Writer
{
Stream m_s;

public:
Writer(std::istream& s) : m_s(s) {}
write(char* text)
{
m_s << text << std::endl;
++this->total_messages;
}
} m_out = {std::cout}, m_err = {std::cerr};
};

Server::bar()
{
// ...
if (success)
out.write("I feel happy");
else
err.write("Nine pence");
}

Not exactly realistic, but it's a maximally simplified example.
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.

Edward: ping?

--
Matthew

Nicol Bolas

unread,
Sep 10, 2015, 10:04:37 PM9/10/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, September 9, 2015 at 5:58:26 PM UTC-4, Matthew Woehlke wrote:
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.

What do you mean by "reading data"? The object is stateless. Just like a struct with no members, it has no (non-stateless) NSDMs to "read".
 
>> 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?

ISO/IEC 14882:2011, Standard For Programming Language C++, section 3.9, p3:

-> For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, 41 obj2 shall subsequently hold the same value as obj1.

Notice how this paragraph explicitly forbids memcpying base class subobjects. That's part of how EBO is permitted. Empty objects still technically have a size and are trivially copyable types. But it isn't legal to memcpy from or to base class subobjects, so you never have to deal with the issue of copying from/to something that has no size.

Something similar could be said for stateless object trivial copyability. That is, memcpying isn't allowed if the source or destination object is a non-static member subobject. You can memcpy the containing object, but not the NSDM subobjects, if they are stateless.

Compilers can still optimize space for stateless non-NSDM variables, or at least concatenate all of them into a single memory location.

And, more importantly, why would the
world end if we tweaked that definition for inner classes?

Because it is not necessary. Limitations should only exist if they're outside of the design space or if it would be unimplementable without them.

Consider the case I just mentioned, about forbidding memcpy on stateless NDSM subobjects. That's done solely to allow implementations the ability to give stateless NDSMs zero space. Just like with EBO and memcpying member subobjects.

If it's possible for compilers to implement inner class NSDMs so that they can be trivially copyable, then we should make them trivially copyable (where applicable, of course).
 
>>>> 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 };

I'll assume you missed the template argument there, but your general point is made.

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.

Fair enough.

Matthew Woehlke

unread,
Sep 11, 2015, 10:57:05 AM9/11/15
to std-pr...@isocpp.org
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? (Not talking about the actual data that ends up in
'somewhere', which presumably is unspecified, but i.e. that it won't
SEGV, launch missles, etc.) If it isn't UB, I would think that any
stateless object would need to have "real storage".

If it's UB, then 'src' could be anything, including null.

>> On 2015-09-09 15:50, Nicol Bolas wrote:
>>> There's a term for types where memcpy results in UB: *not trivially
>>> copyable*.
>>
>> Okay... where is that specified?
>
> ISO/IEC 14882:2011, Standard For Programming Language C++, section 3.9, p3:
>
> -> For any trivially copyable type T, if two pointers to T point to
> distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a
> base-class subobject, if the underlying bytes (1.7) making up obj1 are
> copied into obj2, 41 obj2 shall subsequently hold the same value as obj1.
>
> Notice how this paragraph explicitly forbids memcpying base class
> subobjects. That's part of how EBO is permitted. Empty objects still
> technically have a size and are trivially copyable types. But it isn't
> legal to memcpy from or to base class subobjects, so you never have to deal
> with the issue of copying from/to something that has no size.
>
> Something similar could be said for stateless object trivial copyability.
> That is, memcpying isn't allowed if the source or destination object is a
> non-static member subobject. You can memcpy the containing object, but not
> the NSDM subobjects, if they are stateless.

I think I'd extend that to *any* stateless objects. There's no reason to
copy them in the first place. (And I think it addresses the above.)

(Don't forget to make the same change to 3.9¶2 also :-).)

>> And, more importantly, why would the world end if we tweaked that
>> definition for inner classes?
>
> Because it is not necessary. Limitations should only exist if they're
> outside of the design space or if it would be unimplementable without them.

Define "necessary". 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.

OTOH, maybe doing that is less desirable than supporting true arrays of
inner classes.

I'm still not convinced it's *impossible*, but maybe I am convinced it's
*impractical* :-).

>>>>>> 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 };
>
> I'll assume you missed the template argument there, but your general point
> is made.

...IIRC, I was being optimistic about template argument deduction ;-).
(Perhaps unreasonably so...)

Anyway, I'm fine if we don't have this. It's more something to keep in
reserve that could mitigate the prohibition on arrays. (If we aren't
able to lift it anyway via some resolution of the above TC discussion.)

--
Matthew

Nicol Bolas

unread,
Sep 11, 2015, 1:12:35 PM9/11/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Friday, September 11, 2015 at 10:57:05 AM UTC-4, Matthew Woehlke wrote:
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?

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.

Remember: empty base optimization is not required, except for standard layout rules. Similarly, stateless optimization is not required, except for member subobjects. The compiler could give all static-duration stateless objects the same memory location. The compiler could give automatic-duration stateless objects no location, unless the user gives them one. Or it could give them the global, static-duration location. Or one location on the stack just like regular empty objects.
 

And you lose the whole point of trivial copyability: the ability to do a memcpy on it.

This isn't like the exception made for base subobjects with respect to memcpy. In that case, it's not the type itself that's the problem; it's a specific use-case: being a base subobject. Here, every instance of an inner class must be a member subobject. Therefore, while you can "say" that it's trivially copyable, you can never actually trivially copy it.

So what's the point of saying it's trivially copyable... if you can't actually copy it trivially?

Matthew Woehlke

unread,
Sep 11, 2015, 1:54:35 PM9/11/15
to std-pr...@isocpp.org
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"? (Note: I'm not sure what I
was thinking when I specified "member" there; I meant to include
non-members e.g. globals and local statics also. I guess non-static
locals - or even all locals - could depend on if their address is ever
taken.)

To be clear, this is by no means an objection, just a clarification. I'm
okay with either behavior, just so long as it's clear what is allowed.

>> 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? The only instances I can think
of where an explicit memcpy is useful are in container types, where T
will never be an inner class type.)

--
Matthew

Nicol Bolas

unread,
Sep 12, 2015, 11:36:53 AM9/12/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Friday, September 11, 2015 at 1:54:35 PM UTC-4, Matthew Woehlke wrote:
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?

Yes.

 
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"?

Every object has storage, so that question was answered by the standard. Variables of stateless types are objects, no matter where they are declared. Therefore, they have storage.

The question is whether they have unique storage.

Compilers are allowed to give objects storage or not, within the limits of observable behavior. A compiler may optimize the placement of an `int` into a register, so that it never has actual "storage" in memory. This would only be possible if you never take the address of that integer.

The same would be true of stateless types. If you never take the address of a non-NSDM stateless object, the compiler doesn't have to give it storage. If you do take the address of such an object, the compiler will have to ensure that it has some address.

But stateless objects are not required to have unique addresses. So in the above case, if the compiler needs to give storage to a non-NSDM stateless object, the compiler could give it the exact same storage that it gives all such objects.

>> 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.

You can believe whatever you like, but the actual reasoning for the change is quite clear. It's there so that users can do `memcpy` on objects rather than calling contructors. `std::vector` implements this as an optimization when copying them or reallocating storage.

Outside of defining what types are trivially copyable, that trivially copyable objects can be copied via `memcpy` is the only thing the standard says about them. So it's kinda hard to see how the main point of something isn't the only thing that is said about them.

The old C++98/03 rules allowed such copying only for POD types. The trivial copyable distinction was introduced because POD was too big of a restriction, that compilers could allow more complex objects to be copied bytewise.

How the compiler implements the default copy constructor was not a factor in the creation of this category. After all, the user can't tell the difference between a copy-constructor-via-memcpy and a copy-constructor-via-memberwise-copy. That's optimization, not apparent behavior.

And even then, I'd bet that compilers will decide on memberwise-copies when a bytewise-loop would be too expensive. That's all internal compiler optimizations; they have nothing to do with the trivial copyability category.

Or to put it another way, people aren't using metaprogramming to check to see if something is trivially copyable so that they can tell when a copy constructor will be "fast" or "slow". It's to tell if they need to use the copy constructor at all.

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.

OK, the trivial copyability rules are pretty simple as they stand now. In particular, the rule for NSDMs and base classes is, well, trivial: they must themselves be trivially copyable.

What you're proposing is to take this very simple rule and highly complicate it:

-> All NSDMs must be trivially copyable, except for NSDMs of inner class types. A class may be trivially copyable with inner class NSDMs if those types would satisfy all of the requirements of trivial copyability, save the one forbidding inner classes from being trivially copyable.

So a class is trivially copyable even though it has members that aren't themselves trivially copyable. Except that those non-trivially copyable member can still affect trivial copyability of the other class, if the reason why they're not trivially copyable is one of a subset of the prohibitions of trivial copyability.

Go ahead and explain that to somebody...

- The inner class copy/move/assign may still have a default generated
  implementation that is a simple memcpy.

I think you're misunderstanding something here.

It doesn't matter whether the default copy constructor can be implemented as a memcpy. That's not what trivial copyability is about. It's about whether calling the copy constructor is completely equivalent to `memcpy(..., sizeof(T))`.
 
...and the user can still copy *one* inner class type (but not an array
thereof) via memcpy, albeit the arguments must be non-standard.

The same misunderstanding is at play here. If the copy size is not `sizeof(T)`, then it's not a trivial copy.

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?

Just off the top of my head, binary serialization.
Reply all
Reply to author
Forward
0 new messages