Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

Fixing properties and optional parens in a straightforward way.

Version 1.1
Created 2013-02-06
Last modified 2013-03-13
Author Timon Gehr


The DIP23 draft does not address all requests and only deals with a subset of cases. Furthermore, it is specified by example and has a lot of emphasis on special cases. This proposal attempts to give simple, general, comprehensive and implementable rules. The proposal is divided into three stages. The first stage is given by the next two subsections. It is also the most fleshed-out part. Its scope is about the same as DIP23’s.

The other two stages specify the “semantic rewrite” feature that was requested a few times on the newsgroup to different extents. They are mostly independent of the first stage and can therefore be approved or rejected independently of the first stage.

Optional parens

A symbol ‘foo’ that refers to a non-@property-qualified function or function template is rewritten to foo() iff it does not occur in one of the following contexts:

 1. Function call position ( foo(args) )  2. Address-taken position ( &foo )  3. Instantiation position ( foo!args )  4. Template argument position ( tmpl!(args1, foo, args2) )  5. Aliased symbol position (alias sym = foo;,  alias foo sym;)


 1. Redundant parentheses are assumed to be dropped in the parsing stage.  2. If a ternary-expression occurs in a context, both branches are assumed to occur in that context.  3. If a comma-expression occurs in a context, the second sub-expression is assumed to occur in that context.

@property: basic design

A global @property function may have one or two arguments. (those are intended to be used UFCS-style only) A member @property function (static or instance) may have zero or one argument. (additionally to the implicit this pointer, if there is any)

(For templated functions, this restriction is checked after template instantiation.)

In the first cases, the function is a @property getter. In the second cases, it is a @property setter.

It is illegal to overload @property-qualified functions against non-@property-qualified functions.

The following rewrite is assumed to be always applied on the entire AST without loss of generality: (this gets rid of an annoying border case)

 (exprs, b) (op)= exp is rewritten to (exprs, b (op)= exp)

The UFCS case is not discussed in detail, but it is straightforward.

The following __traits is introduced:

 __traits(propertyAccessors, propertySymbol)

This expression behaves like the propertySymbol, except that it is not subject to any of the rewrite rules below.

A symbol ‘prop’ that refers to a @property-qualified function or function template is rewritten to __traits(propertyAccessors, prop)() iff it does not occur in one of the following contexts:

 1. If is(typeof(__traits(propertyAccessors, prop)(exp))): Assignment position: prop = exp  -- the whole expression is rewritten to __traits(propertyAccessors, prop)(exp)

 2. Aliased symbol position: (alias sym = prop;,  alias prop sym;)  -- In this case, no rewrite takes place.

 3. Template alias parameter: ( tmpl!(args1,prop,args2) )  -- The rewrite is reverted after the property has been matched to a template alias parameter. (Note: alias parameters are always preferred to value parameters when matching on a non-enum symbol.)

@property: possible extensions

Rule 1 could be changed to:

 1. If is(typeof(__traits(propertyAccessors, prop)(exp))): Assignment position: prop = exp  -- - typeof(__traits(propertyAccessors, prop)(exp)) is void and its result is used       - the result of the expression is used in lvalue position => The whole expression is rewritten to (__traits(propertyAccessors, prop)(exp), prop)       - the result of the expression is used in rvalue position => The whole expression is rewritten to { auto val=exp; prop=val; return val; }() where 'val' is chosen such that it does not occur free in 'exp' or 'prop' and is treated as an rvalue if exp is an rvalue.  -- - otherwise, the whole expression is rewritten to __traits(propertyAccessors, prop)(exp)

Everything below should be appended to the proposal if the consensus is to support simple “semantic rewrites”:

 4. OpAssign position: prop op= exp  -- In this case, the whole expression is rewritten to {auto val=prop; val op= exp; return prop=val; }(), where 'val' is choosen such that it does not occur free in exp or prop.

 5. Prefix increment/decrement position: ++prop or --prop  -- In this case, the whole expression is rewritten to prop+=1 or prop-=1 respectively.

 6. Suffix increment/decrement position: prop++ and prop--  -- In this case, the usual rewrites for prop++ and prop-- are applied before prop is rewritten.

Everything below should be appended to the proposal if the consensus is to support full “semantic rewrites”:

The following cases only apply if typeof(prop) can be successfully evaluated to a value type. i.e. a type that is not a class reference, pointer, slice, function pointer, or delegate.

 7. IndexAssign and OpIndexOpAssign: prop[indexes] (op)= exp  -- In this case, the whole expression is rewritten to {auto val=prop; val[indexes] (op)= exp; return prop=val; }(), where 'val' is choosen such that it does not occur free in exp or prop.

 8. Field access position: (context) prop.ident (context), if the context corresponds to any one context from 1 to 6, but not only 6  -- In this case, the expression is rewritten to {auto val1=prop, val2=(context) val1.ident (context); prop=val1; return val2 }(), where 'val1'/'val2' are choosen such that they does not occur free in exp or prop or in each other.

(This last rule is formulated in a somewhat informal way and could be stated more rigorously if the need arises.)

Applicability of “semantic rewrites” to operator overloading

The “semantic rewrite” strategy would probably need to be employed for operator overloading as well:

 S s; // s is a struct, s.x is a field  ++s[i].x;

the last expression would be rewritten according to almost identical rules to

 {auto val1=s[i], val2=++val1.x; s[i]=val1; return val2; }()

A note on implementation

The compiler is of course free to use more efficient strategies that lead to the same semantics. Error messages should ideally not expose the rewritten code.