properties?

139 views
Skip to first unread message

Evan Teran

unread,
Aug 9, 2017, 1:32:28 PM8/9/17
to ISO C++ Standard - Future Proposals
I never brought it up before because I assumed that it MUST have been discussed in the past... but a search turns up nothing. Is there any thoughts about introducing properties to classes/structs/unions? Like lambdas, I kinda envision them as syntactic sugar for public member variables which overload cast operators and operator=. I can imagine something like this (forgive adhoc syntax I'm making up on the spot..., I think that the specific syntax is less important than whether or not the feature is desirable)


class T {
public:
   
[[property]] int data = [ read_data, write_data ];
public:
   
int read_data() const { return data_; }
   
void write_data(int n) { data_ = n; }
private:
   
int data_;
};


Not pretty, but I suspect that we can come up with something better. The idea is that this would be roughly equivalent to the following code:



class T {
public:
   
struct data_impl {
        T
& operator=(int n) {
           
self->write_data(n);
           
return *self;
       
}
       
operator int() const {
           
return self->read_data();
       
}
        T
*self;
   
} data = { this };
public:
   
int read_data() const { return data_; }
   
void write_data(int n) { data_ = n; }
private:
   
int data_;
};

The idea being of course that this makes code such as the following valid:

    T value;
    value
.data = 10;
    std
::cout << value.data << "\n"

I can imagine a few benefits for properties:

  1. If we encourage the use of properties over named getters and setters, then it makes long term maintenance of code simpler with better API stability. We can write code initially just using public member syntax if there is no need for special handling on reading or writing of the member. If the need ever arises to have special handling during read or write, then it the code only needs to change in the class itself. The code which uses it need not change at all (obvious exceptions involving taking the address of a member, but taking the address of a property could just be a hard error since it doesn't make much sense in the typical case).
  2. Can replace a lot of uses of bifields which often result in undefined or platform specific behaviors. A user could create properties which represent each bit and can do the masking themselves to ensure in a reliable way that the bits they intend to work with are the ones which actually get used regardless of endian issues.
Like I said, I'm less interesting in the bikeshedding of syntax concerns, but more interested in the relative merits and concerns with this type of feature.

Matthew Woehlke

unread,
Aug 9, 2017, 1:59:24 PM8/9/17
to std-pr...@isocpp.org, Evan Teran
On 2017-08-09 13:32, Evan Teran wrote:
> I never brought it up before because I assumed that it MUST have been
> discussed in the past...

It has. To death. Properties are contentious.

(Nicol and I came up with something maybe a year ago that seemed
plausible and had additional benefits, but I don't know if it ever got
written up...)

> but a search turns up nothing.

Um...

https://groups.google.com/a/isocpp.org/forum/#!searchin/std-proposals/properties%7Csort:relevance

> I think that the specific syntax is less important than whether or
> not the feature is desirable)
A number of folks seem to be interested, but the implementation seems to
be contentious, from what I recall.

> [[property]] int data = [ read_data, write_data ];

FWIW, I'm pretty sure this syntax won't fly. Also, while there are
significant advantages to this simplistic approach, I'm sure some folks
will complain (loudly) that it requires the get/set functions are still
direct members of the class, and there may be difficulties knowing which
one to call when.

Do please also read the inline class threads...

--
Matthew

Nicol Bolas

unread,
Aug 9, 2017, 4:20:11 PM8/9/17
to ISO C++ Standard - Future Proposals, evan....@gmail.com


On Wednesday, August 9, 2017 at 1:59:24 PM UTC-4, Matthew Woehlke wrote:
On 2017-08-09 13:32, Evan Teran wrote:
> I never brought it up before because I assumed that it MUST have been
> discussed in the past...

It has. To death. Properties are contentious.

(Nicol and I came up with something maybe a year ago that seemed
plausible and had additional benefits, but I don't know if it ever got
written up...)

I poked around with the ideas for a while. The basic idea was to provide 3 independently-useful features that, when combined, would allow you to develop a zero-overhead, low-boilerplate library implementation of properties. Those features being stateless member subobjects (so that the property would just be a member), inner classes (so that the property could just be an inner class, which would have access to its owning subobject, no matter where it was), and language-support for CRTP-base class (so that you could write the implementation of the inner class outside of the inner class itself, and thus reuse boilerplate).

My biggest stumbling block was the latter. I realized that I was basically creating "mixins," but though inheritance. The problem there is that the derived class is incomplete until the template instantiation of the base class, so the CRTP-base class can only access features of the derived class within functions. That made doing simple things like iterator traits impossible without some external class (like `iterator_traits`).

So if I wanted to do mixins without a bunch of arbitrary limitations, they'd need to not just be some base class using template trickery, but a true, first-class feature that was separate from template instantiation.

Cleiton Santoia

unread,
Aug 9, 2017, 7:33:10 PM8/9/17
to ISO C++ Standard - Future Proposals

Just read Metaclasses ;) than see if you get some ideas...


BR
Cleiton


Bo Persson

unread,
Aug 9, 2017, 10:12:36 PM8/9/17
to std-pr...@isocpp.org
On 2017-08-09 19:32, Evan Teran wrote:
> I never brought it up before because I assumed that it MUST have been
> discussed in the past...

Oh, yeah...

>
> The idea being of course that this makes code such as the following valid:
>
> |
> T value;
> value.data =10;
> std::cout <<value.data <<"\n"
> |
>

This could already work if you just add () to each use of data. :-)


> I can imagine a few benefits for properties:
>
> 1. If we encourage the use of properties over named getters and
> setters, then it makes long term maintenance of code simpler with
> better API stability. We can write code initially just using public
> member syntax if there is no need for special handling on reading or
> writing of the member. If the need ever arises to have special
> handling during read or write, then it the code only needs to change
> in the class itself. The code which uses it need not change at all
> (obvious exceptions involving taking the address of a member, but
> taking the address of a property could just be a hard error since it
> doesn't make much sense in the typical case).

A counter argument here is that adding "special handling", like
validating the assigned value, IS an API change.

Adding LOTS of processing changes the runtime characteristics of the
code, which could also be considered an API change.

> 2. Can replace a lot of uses of bifields which often result in
> undefined or platform specific behaviors. A user could create
> properties which represent each bit and can do the masking
> themselves to ensure in a reliable way that the bits they intend to
> work with are the ones which actually get used regardless of endian
> issues.
>

This offers a solution to a problem I have never had.



Bo Persson


Evan Teran

unread,
Aug 10, 2017, 1:02:15 AM8/10/17
to ISO C++ Standard - Future Proposals, b...@gmb.dk
Hmm, I don't know why when i typed "properties" into the search it came up empty, maybe an unlucky network quirk :-/. Regardless, I see them now, and yes, it does seem that they have been discussed numerous times in many different ways.

@Matthew, yea, I was fairly certain that the syntax was not viable, only used it for illustrative purposes. I am certain that better syntax could be developed that would be more robust. I will certainly take a look at discussing involving inline classes. That's for the pointer on that one.

@Nicol, sounds interesting. If inner classes allow a library solution, that sounds great. I've toyed with some library solutions, but at the moment, it takes a bunch of macro usage to make them remotely "pretty".

@Bo, Yes, Of course I could just add the () and they'll "just work". it is true that this is just syntax. But I think better syntax is sometimes a worthy goal if it makes the code better. I see what you are saying about changing the performance characteristics of "reading a member" would be viewed as an API change. I do however think it's a matter of perspective. Any function or method can become arbitrarily more complex between versions; if the way to use it remains the same, and the result is effectively the same, then I would at least call it "compatible" with the previous API. Sure, it's different, if it's more expensive but it is conceptually compatible. I'd leave it to the library writers to inform users if there is a catastrophic change in performance.

As for the bitfield replacements, I understand that it's not a problem that you've encountered, but when dealing with (or emulating) hardware it can be a concern. I need a specific bit to be set, and I'd love to give it a name, so the code is nice and readable. But it's quite easy to fall into the land of compiler specific or arch specific behaviors. It would be nice if I had the ability to do this portable between compilers and compiler versions.

Anyway.

Bottom line is that I see that this has been discussed... a lot. And I have a lot of reading to do if I am wanting to have a change of making an informed contribution to the discussion.

Thanks for humoring my post.
Evan

Nicol Bolas

unread,
Aug 10, 2017, 2:19:00 AM8/10/17
to ISO C++ Standard - Future Proposals, b...@gmb.dk
On Wednesday, August 9, 2017 at 10:12:36 PM UTC-4, Bo Persson wrote:
On 2017-08-09 19:32, Evan Teran wrote:
> I never brought it up before because I assumed that it MUST have been
> discussed in the past...

Oh, yeah...

>
> The idea being of course that this makes code such as the following valid:
>
> |
>      T value;
>      value.data =10;
>      std::cout <<value.data <<"\n"
> |
>

This could already work if you just add () to each use of data.  :-)

Would it? Would this:

some_function(value.data());

Would that allow `some_function` to set `data`'s value? To do that, you would need to make `data()` return a proxy object, which can be converted into the appropriate type, but can also be assigned to the appropriate type and otherwise treated exactly like a reference to the other type.

That's hardly "just" adding something.

Ville Voutilainen

unread,
Aug 10, 2017, 2:35:17 AM8/10/17
to ISO C++ Standard - Future Proposals, b...@gmb.dk
Gee, data() could just return a reference.

Matthew Woehlke

unread,
Aug 10, 2017, 10:05:17 AM8/10/17
to std-pr...@isocpp.org, Ville Voutilainen, b...@gmb.dk
...which is only possible if it is okay for the user to directly assign
to the underlying data member. If something non-trivial needs to happen
when the property is assigned, then no, you'd need to return a proxy
object. (And, alas, some_function would need to be a template.)

--
Matthew

Matthew Woehlke

unread,
Aug 10, 2017, 10:08:50 AM8/10/17
to std-pr...@isocpp.org, Nicol Bolas, evan....@gmail.com
On 2017-08-09 16:20, Nicol Bolas wrote:
> On Wednesday, August 9, 2017 at 1:59:24 PM UTC-4, Matthew Woehlke wrote:
>> On 2017-08-09 13:32, Evan Teran wrote:
> I poked around with the ideas for a while. The basic idea was to
> provide 3 independently-useful features [...] stateless member
> subobjects [...] inner classes [...] and language-support for
> CRTP-base class (so that you could write the implementation of the
> inner class outside of the inner class itself, and thus reuse
> boilerplate).>
> My biggest stumbling block was the latter. I realized that I was basically
> creating "mixins," but though inheritance. The problem there is that the
> derived class is incomplete until the template instantiation of the base
> class, so the CRTP-base class can only access features of the derived class
> within functions. That made doing simple things like iterator traits
> impossible without some external class (like `iterator_traits`).
>
> So if I wanted to do mixins without a bunch of arbitrary limitations,
> they'd need to not just be some base class using template trickery, but a
> true, first-class feature that was separate from template instantiation.

Ah. Of course, mixins are sufficiently wide-spread that figuring those
out is almost certainly worthwhile in its own right. (I've always
thought so, anyway.)

Maybe someone would be interested in taking up the torch on those?

--
Matthew

Nicol Bolas

unread,
Aug 10, 2017, 10:18:13 AM8/10/17
to ISO C++ Standard - Future Proposals, ville.vo...@gmail.com, b...@gmb.dk

Not to mention that it assumes that there is actually an object behind `data()` that could be returned as a reference. Many properties are phantasmal; the actual data is not stored in the type it appears as. Indeed, one could say that the whole point of properties as a feature are that they can be phantasmal in such a way, without the user being aware of their nature.

Matthew Woehlke

unread,
Aug 10, 2017, 10:40:34 AM8/10/17
to std-pr...@isocpp.org, Nicol Bolas, ville.vo...@gmail.com, b...@gmb.dk
> that they *can be* phantasmal in such a way, without the user being aware
> of their nature.

...at least until someone tries to take/pass it as a reference to the
"apparent" type ;-). In a strongly-typed language like C++, that's just
insurmountable. (But I don't consider it a feature-killer.)

Applying this to bitfields was mentioned earlier in the thread...

--
Matthew

Ville Voutilainen

unread,
Aug 10, 2017, 10:40:58 AM8/10/17
to ISO C++ Standard - Future Proposals
On 10 August 2017 at 17:08, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> Ah. Of course, mixins are sufficiently wide-spread that figuring those
> out is almost certainly worthwhile in its own right. (I've always
> thought so, anyway.)
>
> Maybe someone would be interested in taking up the torch on those?


I daresay we should see how much we can get via reflection, and then
figure out whether we want
to add specific facilities.

Nicol Bolas

unread,
Aug 10, 2017, 12:21:23 PM8/10/17
to ISO C++ Standard - Future Proposals

This is not a problem reflection can solve. Or at least, not the problem I was encountering with CRTP-based mixins. That problem being the fact that the derived class (the class being mixed into) is incomplete at the point where the mixin is being instantiated.

That's the catch-22: you need a way to augment a type with new stuff, but you also need a way to access the stuff that was previously added to the type. And as I understand, you cannot invoke reflection on an incomplete type.

And even if you could reflect over a partially complete type, you wouldn't be able to access members that haven't been declared yet. And since CRTP-based mixins are used as base classes, then pretty much everything about the derived class has not yet been declared.

A proper mixin facility needs the following three properties:

1: The ability for a type to add the mixin to itself, such that the mixin's declarations are able to have access to any previously-declared members in the type.

2: The ability for a type that gets mixed-into to access the mixin's declarations as if they were its own.

3: The ability to convert a pointer/reference to a type into a pointer/reference to the mixin it uses, providing access only to the mixin's interface.

Note: I'm not sure that #3 is really needed. But at the same time, it seems like an exceedingly useful thing to have, since it allows you to have external operator overloading and the like.

Ville Voutilainen

unread,
Aug 10, 2017, 12:35:59 PM8/10/17
to ISO C++ Standard - Future Proposals
On 10 August 2017 at 19:21, Nicol Bolas <jmck...@gmail.com> wrote:
> On Thursday, August 10, 2017 at 10:40:58 AM UTC-4, Ville Voutilainen wrote:
>>
>> On 10 August 2017 at 17:08, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>> > Ah. Of course, mixins are sufficiently wide-spread that figuring those
>> > out is almost certainly worthwhile in its own right. (I've always
>> > thought so, anyway.)
>> >
>> > Maybe someone would be interested in taking up the torch on those?
>>
>>
>> I daresay we should see how much we can get via reflection, and then
>> figure out whether we want
>> to add specific facilities.
>
>
> This is not a problem reflection can solve. Or at least, not the problem I
> was encountering with CRTP-based mixins. That problem being the fact that
> the derived class (the class being mixed into) is incomplete at the point
> where the mixin is being instantiated.
>
> That's the catch-22: you need a way to augment a type with new stuff, but
> you also need a way to access the stuff that was previously added to the
> type. And as I understand, you cannot invoke reflection on an incomplete
> type.

You can run metaprograms on a type that is so-called almost-complete,
and the metaprogram completes it.
See http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0707r1.pdf.

> And even if you could reflect over a partially complete type, you wouldn't
> be able to access members that haven't been declared yet. And since

Except that you can, if you postpone instantiation of such code to a
time when the members
are available. You generate a template, and then instantiate the
template with the member. The member
does need to be a parameter, but that doesn't change the mixed-in code.

> A proper mixin facility needs the following three properties:
>
> 1: The ability for a type to add the mixin to itself, such that the mixin's
> declarations are able to have access to any previously-declared members in
> the type.

Right, none of the metaprogramming proposals I've seen thus far
support exactly this. The mixin
must accept the type it's going to be mixed in as a parameter of some
kind. We don't have
'inline templates' where we can just explode code into a context, and
have the exploded code
expect certain declarations to be valid.

> 2: The ability for a type that gets mixed-into to access the mixin's
> declarations as if they were its own.

This is another aspect which is not that direct in the proposals I've seen.

> 3: The ability to convert a pointer/reference to a type into a
> pointer/reference to the mixin it uses, providing access only to the mixin's
> interface.

This is easy to do, just generate a conversion function or an operator.

With the proposed metaprogramming facilities, we can fairly easily
express "make type X behave like
type Y, without requiring an inheritance relationship". We can
generate delegation code. What we
don't have is "make the code in type Y behave differently depending on
its surrounding context, without
having any readiness for that in type Y".

Matthew Woehlke

unread,
Aug 10, 2017, 12:35:59 PM8/10/17
to std-pr...@isocpp.org, Ville Voutilainen
I don't think *reflection* can help with CRTP... *metaclasses* on the
other hand...

--
Matthew

Ville Voutilainen

unread,
Aug 10, 2017, 12:36:56 PM8/10/17
to Matthew Woehlke, ISO C++ Standard - Future Proposals
On 10 August 2017 at 19:35, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> I don't think *reflection* can help with CRTP... *metaclasses* on the
> other hand...


I consider code generation a part of reflection. Introspection is
different, and more limited. :P

Matthew Woehlke

unread,
Aug 10, 2017, 12:53:58 PM8/10/17
to std-pr...@isocpp.org
On 2017-08-10 12:36, Ville Voutilainen wrote:
> On 10 August 2017 at 19:35, Matthew Woehlke wrote:
>> I don't think *reflection* can help with CRTP... *metaclasses* on the
>> other hand...
>
> I consider code generation a part of reflection. Introspection is
> different, and more limited. :P

Ah, I see... language impedance ;-).

(I suspect Nicol and I have the same "understanding" of what
"reflection" means... i.e. to us, P0707 *builds on* reflection, but
isn't *part of* reflection.)

--
Matthew

Ville Voutilainen

unread,
Aug 10, 2017, 1:00:01 PM8/10/17
to ISO C++ Standard - Future Proposals
Well, what is part of reflection remains to be seen. We don't have
anything published yet, and
there's been some serious thought to go towards something that's less
template-y than the first reflection
proposals were. In addition to that, we are seriously looking into
what code generating compile-time
facilities should be like (and look like). It seems clear that we want
code transformation and generation
capabilities. Whether metaclasses will be part of that picture is
another matter.

Nicol Bolas

unread,
Aug 10, 2017, 1:14:32 PM8/10/17
to ISO C++ Standard - Future Proposals

I guess it's more a matter of the fact that it's a very far future thing. C++20 is highly unlikely to see purely introspective reflection. So the chance of code generation reflection reaching even C++23 is pretty low.

If mixins are a good idea for C++, I just don't see a need for us to hold off until C++23 (at the earliest), just because we think we might be able to get them via code generation reflection. If it were right around the corner, that'd be one thing. But when that sort of stuff is still in the design/research project stage, then let's see if we can solve the problem in a more direct way.

At least in the meantime.

Ville Voutilainen

unread,
Aug 10, 2017, 1:38:25 PM8/10/17
to ISO C++ Standard - Future Proposals
On 10 August 2017 at 20:14, Nicol Bolas <jmck...@gmail.com> wrote:
> I guess it's more a matter of the fact that it's a very far future thing.
> C++20 is highly unlikely to see purely introspective reflection. So the
> chance of code generation reflection reaching even C++23 is pretty low.

Considering that there's already implementation experience on both
introspective reflection
and code generation, it's safe to say that that's far from a matter of fact.

> If mixins are a good idea for C++, I just don't see a need for us to hold
> off until C++23 (at the earliest), just because we think we might be able to
> get them via code generation reflection. If it were right around the corner,
> that'd be one thing. But when that sort of stuff is still in the
> design/research project stage, then let's see if we can solve the problem in
> a more direct way.
> At least in the meantime.

If we'd have a pressing need for mixins, or confidence that we can't
get close enough
to them via the reflection facilities, that might be plausible.
Neither being the case, there's
no need to rush into a specific mixin facility.

Cleiton Santoia

unread,
Aug 10, 2017, 2:09:39 PM8/10/17
to ISO C++ Standard - Future Proposals
Ah, I see... language impedance ;-).

(I suspect Nicol and I have the same "understanding" of what
"reflection" means... i.e. to us, P0707 *builds on* reflection, but
isn't *part of* reflection.)


Introspection + transform + injection = reflection

BR

 

Nicol Bolas

unread,
Aug 10, 2017, 3:12:13 PM8/10/17
to ISO C++ Standard - Future Proposals
On Thursday, August 10, 2017 at 1:38:25 PM UTC-4, Ville Voutilainen wrote:
On 10 August 2017 at 20:14, Nicol Bolas <jmck...@gmail.com> wrote:
> I guess it's more a matter of the fact that it's a very far future thing.
> C++20 is highly unlikely to see purely introspective reflection. So the
> chance of code generation reflection reaching even C++23 is pretty low.

Considering that there's already implementation experience on both
introspective reflection
and code generation, it's safe to say that that's far from a matter of fact.

Sure, that's possible. But the statistical evidence from the committee's behavior strongly suggests otherwise.

Concepts took about 4 years to go from initial design to a finished TS. While there had been some talk about modules before N4047 in 2014, it still took 3 years to take that proposal to the PDTS phase, putting it on Concepts TS's pace. The Coroutines TS took even longer, finally landing as a TS after 6 years work.

Introspective reflection? That started in 2014, and they're still arguing about the interface. Don't get me wrong; that's a good argument to have. I'm just saying that it's moving slowly much more slowly than other proposals. It's looking more on pace to match Coroutines than Concepts.

And that's just measuring the time to becoming a TS. If you look at the TS wave that entered C++17, we clearly see that the only things that made it were things that reached their TS status by the end of 2014. Filesystem, Parallelism v1, and LibFundamentals v1 all hit that timeframe, and they all got voted in. Concepts, and Concurrency missed that timeframe and had to wait for the next C++ version.

You could argue that there were technical reasons for waiting on those. But this is what we've seen from the committee.

Concepts, Ranges, Networking, and Coroutines are probably going to hit C++20. But Modules is on the same timeframe relative to C++20 that Concepts were relative to C++17. And purely introspective reflection is even farther behind than that.

So unless the intent is to have the various reflection proposals go straight into the standard without a TS phase first, I don't see a reason to expect reflection of any form to be standardized anytime soon.

So while I appreciate your optimism about the committee's ability to quickly adopt features, until there is evidence of that optimism translating into features actually being quickly adopted, I shall remain skeptical ;)

> If mixins are a good idea for C++, I just don't see a need for us to hold
> off until C++23 (at the earliest), just because we think we might be able to
> get them via code generation reflection. If it were right around the corner,
> that'd be one thing. But when that sort of stuff is still in the
> design/research project stage, then let's see if we can solve the problem in
> a more direct way.
> At least in the meantime.

If we'd have a pressing need for mixins, or confidence that we can't
get close enough
to them via the reflection facilities, that might be plausible.
Neither being the case, there's
no need to rush into a specific mixin facility.

Every time you use the CRTP, what you're really doing is using a mixin. So the Range-v3 technique for creating range types easily? That's just a mixin, done in a very unintuitive way.

The need for this feature has been there for quite some time. The need for it as a language feature is to avoid all of the problems associated with it as an idiom (teachability, fragile interface, no compiler checking, etc).
Reply all
Reply to author
Forward
0 new messages