Why R^ instead of cli::handle?

Nicola Musatti asked the following excellent question:

-

*The hat symbol and gcnew could be replaced with a template like syntax, e.g. cli::handle\<R\> r = cli::gcnew\<R\>();*  
  
  

I agree that those are alternatives. Everyone, including me, first pushes hard for
a library-only (or at least library-like) solution when they first start out on this
problem. I think an argument can be made for it, and at one time I did so too.

To me, the killer argument in favor of a new declarator with usage R^  instead
of a library-like cli::handle<R> is its pervasiveness: It will
be by far the most widely used part of all these extensions, as it's the common use
case the vast majority of the time for CLI types (as objects, as parameters, etc.).
This extremely wide use amplifies two particular negative consequences we'd like to
avoid: First, the long spelling (here "handle") could in practice effectively become
a reserved word just because people are liable to widely apply using to
avoid being forced to write the qualification every time (this is worse if the name
chosen is a common name likely to be used for other identifiers or even macros, and
"handle" is a very common name). Second, and worse, the long spelling would also make
the language several times more verbose in a very common case than even the Managed
Extensions syntax was, and that in turn was already verbose compared to other CLI
languages.

Compare five alternatives side by side:

  cli::handle<R> r = cli::gcnew<R>();  // 1:
above suggestion

  handle<R> r = gcnew<R>();  // 2: ditto, with
"using"s

  R __gc* r = new R;  // 3: original MC++ syntax

  R^ r = gcnew R;  // 4: C++/CLI syntax

  R r = new R(); // 5: C#/Java syntax

I think you could make a case for any one of these, depending on your tradeoffs. But
I think a tradeoff that favors usability will favor the last few options.

There are also other issues where having ^ and % declarators/operators
that roughly correspond to * and & enables a
more elegant type calculus. I (or someone on the team) will have to write those up
someday, but consider at some future time when we have full mixed types too: When
we can have a type that inherits from both native and CLR base classes/interfaces,
we will want to be able to pass a pointer to such an object to existing ISO C++ APIs
that take a Base1* and a handle to the same object to existing CLI
APIs that take an Base2^ . Both will be common operations and therefore
both should be distinctly expressible with a terse syntax:

  class NativeBase { };

  // a mixed type

  ref class R
: public NativeBase
, public System::Windows::Forms::Form
{ };

  void NativeFunc( NativeBase* );
void CLIFunc( Object^ );

  R r;                  
// object on the stack

  NativeFunc( &r );  // "give me a *" is spelled "&"
as usual

  CLIFunc( %r );  // "give me a ^" is spelled "%"

In this way, % is to ^ pretty much just as & is
to * . If R^ were instead spelled using a templatelike
syntax, what would be the corresponding code to get at it?

Finally, consider the agnostic template case:

  template<typename T>
void f( T t ) {
SomeBase* b = &t;
 

// I have to have a way of
saying "I want a *" without knowing the type of T

    SomeInterface^ i = %t; // I have to have a way
of saying "I want a ^" without knowing the type of T

  }

I'll write more about the full pointer system in the future. For other design considerations
about handles I'll point to at Brandon's Behind
the Design: Handles
blog entry again, and to my own earlier this week on why
pointers aren't enough by themselves
.