On comp.lang.c++.moderated, Andrew Browne wrote:

  • The goals of the C++/CLI proposal are good ones, I think, but I wonder if it would be possible to achieve them without (most of) the new keywords and semantics?

    For example instead of:

    ref class R {/*...*/};        // CLR reference type
    value class V {/*...*/};     //  CLR value type
    interface class I {/*...*/}; //  CLR interface type
    generic <typename T>
    ref class G {/*...*/};       // CLR generic
    // etc etc

    couldn't we have

    class R : public System::Object {/*...*/}; // CLR reference type
    class V : public System::ValueType {/*...*/}; //  CLR value type
    class I : public System::Object
    {/* pure virtuals only here*/ }; // CLR interface type
    template <typename T>
    class G : public System::Object {/*...*/}; // CLR generic
    // etc etc?

That's one of the alternatives I attempted, and I wasn't the first. I think almost everyone starts here, and I held on for a while before I became convinced I had to let go because it wasn't leading to the right places. Let me share some of the problems and objections that crop up when you work your way down this path:

1. (Minor) Verbose

The above alternative is a lot of typing compared to any of the alternatives (Managed C++ syntax, proposed C++/CLI syntax, and other CLI languages).

There's a pretty easy solution for this one, using keyword shortcuts:

  class R : ref {/*...*/}; // CLR reference type
  class V : value {/*...*/}; //  CLR value type
  class I : interface
    {/* pure virtuals only here*/ }; // CLR interface type

An inconvenience with this is that there could already be a class named ref, and so the syntax would have to be embroided somehow to account for disambiguating this; this is unfortunate but surmountable. But, more importantly, this shorthand still doesn't address the other drawbacks, below, of this general approach.

2. Forward declarations

Consider:

  class X;

Is this a ref class, value class, interface class, or native class? There are a few cases where this needs to be known from the forward declaration.

3. Indirect: The header hunt

Consider:

  class X : public Y { };

Is this a ref class, value class, interface class, or native class? Under the alternative, the only way to know would be to inspect Y and all base classes until you can determine whether any of them directly or indirectly inherit from Object or ValueType (or not). There are shortcuts (e.g., it's simpler for value types because they're always sealed and so the inheritance has to be direct), but the hunt remains.

That may not seem like a huge issue, except that the types really are behaviorally different in small but important ways; for example, in one case a virtual call in a ctor or dtor will be deep, in the other it will be shallow. What metadata will eventually be emitted, if any?

4. Closes doors

Speaking specifically to the last part of the example:

  • template <typename T>
    class G : public System::Object {/*...*/}; // CLR generic

Unfortunately, this conflates the ideas of the type category (ref/value/native) with the form of genericity (generic/template). It says that CLI types can only be genericized, and native types can only be templated, leaving no way to express the other two useful concepts:

  • a templated CLI type (C++/CLI syntax: template<class T> ref class R {};)
  • a generic native type (C++/CLI syntax: generic<class T> class N {};)

Templated CLI types in particular are very useful and are supported in C++/CLI, which lets the template/generic choice and the class category choice vary independently.

5. Other closed doors: Distinguishing mixed types (Future)

In the future, C++/CLI is intended to eventually allow for full mixing and cross-inheritance of arbitrary types. Using the alternative inheritance-based syntax alone does not allow the programmer to distinguish between the following two distinct things that the proposed C++/CLI design lets the programmer express as follows:

  ref class Ref : public ANative { int x; };

  class Native : public ARef { int x; };

This distinction can't be expressed using the proposed alternative above. Both types have System::Object as a base class, but one is a reference class that other CLI languages could use directly and where virtual calls during construction are deep, and one is a native class that other CLI languages can only use via a handle or reference to the ARef base class and where virtual calls during construction are shallow.