Document number:N1600
04-0040
Author:Daveed Vandevoorde
Edison Design Group
Date:11 Feb. 2004

C++/CLI Properties

Introduction

This document presents an extension to the C++ language that is currently being developed by Ecma TC39-TG5 as part of C++/CLI (a variant of C++ that eases the development of programs for Microsoft's .NET framework).

The purpose of this document is to inform WG21 (and the related national bodies) of the direction taken by the C++/CLI work in this area. This document is neither a proposal to include this extension in standard C++, nor an endorsement of this extension by the author.

Basics

C++/CLI properties are entities that are accessed like data members (with various restrictions) but the access is typically translated into calls to accessor functions (which are essentially “getter” and “setter” member functions). For example:

struct Demo1 {
  property int Val {  // Simple scalar property of type int
    int get() const {
      ++Demo1::access_count;
      return this->value;
    }
    void set(int v) {
      ++Demo1::access_count;
      this->value = v;
    }
  }
private:
  int value;
  static unsigned long access_count;
};

int main() {
  Demo1 d;
  d.Val = 3;     // Invokes the "set" accessor function.
  return d.Val;  // Invokes the "get" accessor function.
}
The name of the accessor functions must be get and/or set. Either one (but not both) may be omitted. Omitting an accessor function results in a write-only or read-only property. The address of a property cannot be taken. However, accessor functions are member functions and may therefore be used to form pointer-to-member constants (e.g., &Demo1::Val::set).

Properties can be declared with the keyword virtual, which implies that the accessor functions are virtual. Pure virtual properties are possible too. For example:

struct VirtualProp {
  virtual property int Val = 0 {
    int get() const;          // Pure virtual.
    virtual void set(int v);  // Pure virtual (redundant keyword virtual).
  }
  // ...
};

The examples above shows the simple (and common) case of a nontrivial, nonstatic scalar property. A number of variations on the concept are part of the C++/CLI document. They are explained below.

Motivation

Within the context of Standard C++, properties are essentially syntactic sugar for the common idiom of “getter and setter functions”. Even so, this syntax can help make a source-compatible transition from exposed data members to encapsulated state information.

In the context of more specific run-time frameworks (and specifically, Microsoft's .NET) properties are elements that can be discovered (through reflection/introspection) and modified at run time. For example, modern GUI libraries declare the parameters of their widgets as properties. Visual interface-building tools then load these libraries and present the user with a list of (discovered) properties of the various widgets. When a user modifies a property, the accessor function is invoked, which can for example trigger various GUI-updating events.

(N1384=02-0042 has more details on the connection between properties and some other framework concepts such as events.)

Property Variants

In addition to the simple scalar properties described above, C++/CLI also introduces several other property variants.

Static scalar properties

Static scalar properties are declared with the keyword static. Their accessor functions are static. Static properties are accessed much like static data members (e.g., using the C::P syntax to access a static property P that is a member of a class C).

Trivial scalar properties

The definition of a property (i.e., the brace-enclosed enumeration of its accessor functions) can be replaced by a semicolon. In that case, both get and set accessor functions are synthesized to simply store and retrieve a property value.

For example, the C++/CLI class definition

struct TrivialProp {
  property int Val;
};
is essentially equivalent to
struct TrivialProp {
  property int Val {
    int get() const { return this->__Val; }
    void set(int v) { this->__Val = v; }
  }
private:
  int __Val;
};

Named indexed properties

Named index properties enable access to a collection of values using a syntax reminiscent of access to a field of array type. The following example shows the case of a one-dimensional (named) indexed property:

struct Demo2 {
  property int x[std::string] {
    int get(std::string s) const { ... }
    void set(int v, std::string s) { ... }
  }
  // ...
};

int main() {
  Demo2 d;
  std::string s("CLI");
  d.x[s] = 3;     // Calls Demo2::x::set(3, s)
  return d.x[s];  //  Calls Demo2::x::get(s)
}
Note that indexed properties cannot be static.

Multi-dimensional indexed properties are also possible and they introduce an access syntax that is different from the normal C/C++ array element access syntax. For example:

struct Demo3 {
  property double x[std::string, int] {
    double get(std::string s, int n) const { ... }
    void set(double v, std::string s, int n) { ... }
  }
  // ...
};

int main() {
  Demo3 d;
  std::string s("CLI");
  d.x[s, 7] = 42.0;          // Calls Demo3::x::set(42.0, s, 7)
  return d.x[s, 7] != 42.0;  //  Calls Demo3::x::get(s, 7)
}
The latter example demonstrates how the comma token appearing in the brackets for an indexed property access is treated as an expression separator rather than as the comma operator. (Consequences of this observation are discussed below.)

Default indexed properties

Default indexed properties are like named indexed properties, except that instead of indexing a pseudo-field of the object, the object itself is indexed (almost as if it had a member operator[]). The previous example can be reworked slightly to demonstrate this variant:

struct Demo4 {
  property double default[std::string, int] {
    double get(std::string s, int n) const { ... }
    void set(double v, std::string s, int n) { ... }
  }
  // ...
};

int main() {
  Demo4 d;
  std::string s("CLI");
  d[s, 7] = 42.0;          // Calls Demo4::default::set(42.0, s, 7)
  return d[s, 7] != 42.0;  //  Calls Demo4::default::get(s, 7)
}
Note the use of the keyword default instead of the property name.

Some Technicalities

Ecma TC39-TG5 has studied and resolved several issues arising from the introduction of properties as outlined above. The following are particularly noteworthy.

Multidimensional indexed property access

The expression p->x[2, 3] means something different depending on whether the member x is a property (in which case the comma separates two indices) or something else (in which case the comma is an operator and the expression means the same as p->x[3]). To achieve the effect of a comma operator in a property index, the programmer can use parentheses (e.g., p->x[(2, 3)]).

(Note that in template-dependent expressions this forces an ambiguity that cannot be resolved until instantiation time.)

Property name and type name conflict

The Microsoft .NET framework contains many classes (not originally written using C++/CLI) with a property whose name is spelled identically to the name of the type of the property. For example:

typedef int Color;

struct Conflict {
  property Color Color {  // Property name hides type name
    typename Color get() const;
    void set(typename Color);
  }
  // ...
};
}
As an aid to writing code in such contexts, C++/CLI plans to add syntax using the keyword typename to indicate that nontypes (and in particular: properties) should not be considered when looking up an identifier. The code above uses typename in this new way.

Overloaded indexed properties

Indexed properties can be overloaded. I.e., several named indexed properties with the same name can coexist in the same class provided they can be distinguished based on the arity or types of the indices. Similarly, default indexed properties can be overloaded with other such properties and with the C++ operator[]. Ambiguity and overload resolution rules have been developed to deal with such situations in intuitive ways.

Reserved member names

C++/CLI properties are implemented by synthesizing special members. The names of these members are dictated by the Microsoft .NET framework and must therefore be reserved.

If a class contains a scalar property or a named indexed property with name X, the member names get_X and set_X are reserved within that class. (This is true even when the property contains only one accessor function.)

Similarly, if a class contains a default indexed property, the member names get_Item and set_Item are reserved within that class.

Open Issues

The following questions do not seem to have been answered formally at this time.

See Also...

“PME: Properties, Methods and Events” Borland Software Corp. (N1384=02-0042; 10 Sep. 2002)

“C++/CLI Language Specification (Working Draft 1.1) ” Ecma TC39-TG5 (Ecma/TC39-TG5/2004/3; 10 Jan. 2004)