My C++ Wish List

Here are some features I would have liked to have seen in the C++ standard. They are ordered (in my opinion) from least bizarre to most bizarre! However, even in my most bizarre C++ wishes, I have taken care to make them possible to implement.

1. anonymous struct

To this day, I do not understand how anonymous unions came to be added to the C standard without adding the obvious anonymous struct as well. I am constantly running into situations where I need it. For example:

struct CrossSumSquare { bool isBlack; union { // anonymous union struct { // use these if isBlack==true int hSum, vSum; } b; struct { // use these if isBlack==false NumSet possibleContents; } w; }; }; Wouldn't it be logical if I could leave off the b and w names that I've given the structs! They just get in the way.

Note: With my compiler, if I remove the b and w, it still compiles, but the fields hSum etc. do not exist, ie the compiler simply ignores the whole inner structs.

2. Constructors in unions

The rules for constructors in unions need to be relaxed a bit. The standard does not allow any class which has a constructor to be a member of a union. However, I think such classes should be allowed, so long as they have a constructor that takes no parameters, and that constructor was defined inline to do nothing. For example: struct MyInt { MyInt(int); MyInt() {} int i; }; union Both { int i; MyInt m; // illegal because MyInt has a constructor! };

3. bit_enum

Very often, integers are used to store flags. It is common to use enum to define constants, which can be OR'ed together to form the set of desired flags. However, you cannot use named enumerations for this because the | (OR) operator is not defined on enums, only on ints. Of course unnamed enums have the disadvantage of having no type safety. Here is an example: typedef int WindFlags; enum {sticky=1, noframe=2, jumpy=4, all=sticky|noframe|jumpy}; WindRef NewWindow (WindFlags flags); int main () { WindRef w1 = NewWindow(sticky|noframe); WindRef w2 = NewWindow(all & ~noframe); } A new bit_enum construction would make this sort of situation so much easier to program, and more type-safe: bit_enum WindFlags {sticky, noframe, jumpy, all=sticky|noframe|jumpy}; WindRef NewWindow (WindFlags flags); int main () { WindRef w1 = NewWindow(sticky|noframe); WindRef w2 = NewWindow(all & ~noframe); }

4. return_type

When designing a template library for matrix algebra, I ran into the following problem. Many books use Matrix or Vector as an example, but they all use the same type for both operands of a binary operator, like this: template <typename T> Matrix<T> operator+ (Matrix<T>& p1, Matrix<T>& p2); But what if I want to do something more general, say add a Matrix<double> and a Matrix<int> together? I would want something like this: template <typename T1, typename T2> Matrix<???> operator+ (Matrix<T1>& p1, Matrix<T2>& p2); But what should the return type be?? Well, it should be a matrix of whatever type operator+(T1,T2) returns. As C++ currently stands, I would have to explicitly specify the return type in a third template parameter, which, needless to say, would get ugly pretty quickly.

So I would like to propose the following:

template <typename T1, typename T2> Matrix<return_type(operator+(T1,T2))> operator+ (Matrix<T1>& p1, Matrix<T2>& p2); Notice that I've used a new keyword return_type to find out the return type of a function. It should work on built-in operators as well as user-defined ones.

5. Restricted template matching

Suppose I want to define an operator+ for MyInt, which can take a variety of operands. I can write this as a template function, which I can then specialize to define operator+ for new types: template <typename T> MyInt operator+ (MyInt, T); template <typename T> Complex<T> operator+ (MyInt, Complex<T>); // specialization The problem comes when I form a subclass of Complex: class MyComplex : Complex<double> { MyComplex(double, double); }; main() { MyInt i(1); MyComplex z(1.0, 0.0); i+z; // Oops! Calls operator+ (MyInt, T) } I would like to tell the compiler to match a call to the first operator+ only if no others match. In C++, every time you create a subclass of Complex<> you would have to redefine operator+.

This could be done much more elegantly if we could specify restrictions on the template parameters. Here are three possible ways in which restricted template matching could be used to solve the above problem:

  1. In the first declaration, we could restrict T so that a type matches T only if operator+(int,T) exists. Perhaps we could use the default template parameter syntax for this: template <typename T, function f=operator+(int,T)> MyInt operator+ (MyInt, T);
  2. We could change the second declaration to be more general: template <typename T1, typename T2:Complex<T1> > Complex<T> operator+ (MyInt, T2); This means that T2 can be any type, except it is restricted to being a subclass of Complex<T1>, for some other type T1.
  3. We could change the first declaration to be less general: template <typename T:base_type> Complex<T> operator+ (MyInt, T); Here we have introduced a new keyword base_type to represent the ficticious class from which all types are derived from, including built-in types. At first sight, the additional base_type doesn't seem to be putting any restriction on T at all, since all types are derived from base_type. However, this would change the order of preference for the template matching algorithm, since MyComplex would match both Complex<T> and base_type, but Complex<T> is the better match.

6. Non-static nested (member) classes

Time and time again, I find myself needing member classes that contain a pointer to an instance of the enclosing class. For example: class List { int* data; /*...*/ class Iterator { List* parent; int* item; /*...*/ Iterator (List* parent); operator void* (); operator++ (); }; friend class Iterator; }; List::Iterator::Iterator (List* parent) : parent(parent) , item(parent->data) { } foo(List& list) { for (List::Iterator i(&list); i!=NULL; ++i) { } } What I would like is for the parent parameter in List::Iterator::Iterator to be implicit, rather like a this pointer. The constructor would then invisibly store parent in the Iterator object.

It would go something like this:

class List { int* data; /*...*/ auto class Iterator { //Note the auto //Note the missing parent int* item; /*...*/ Iterator (); //Note the missing parameter operator void* (); operator++ (); }; friend class Iterator; }; List::Iterator::Iterator () : item(data) //was parent->data { } foo(List& list) { for (list.Iterator i; i!=NULL; ++i) { // use list., not List:: } } I have resurrected the old keyword auto, meaning "non-static". Unfortunately, the current C++ standard treats nested classes as static by default, so this was necessary to maintain compatibility. It would have been more logical from a syntax point of view to have made class mean non-static, and static class mean what we have in the current C++ standard.

Notice that fields from the parent class, such as data can be accessed directly. If it is necessary to spell out the names, you would write List::data or this->data or this->List::data. This direct access of parent's data is analogous to ordinary classes, which can access global variables directly. (Think of static storage as being one big class!!)

7. Virtual typedefs

In a few not-so-uncommon situations, I would like a base class to refer to a type that must be defined in its subclasses. In C++, this can be done with methods (called virtual methods), but not with types.

Here are some examples of how types could benefit from the same sort of virtual mechanism:

Example 1: operator=

// My own class that acts like an int class MyInt { int n; MyInt& operator= (int num) {n=num;return *this;} //... }; // A subclass with even more functionality (but with a bug too) class MyIntExtra : public MyInt { void ExtraTask(); }; main() { MyIntExtra n; (n=2).ExtraTask(); // Oops! Doesn't compile! } The problem of course is that (n=2) is of type MyInt, not MyIntExtra, so ExtraTask() is not a method. This problem must be solved by adding an operator= to the definition of class MyIntExtra so that it returns a MyIntExtra instead of a MyInt. This is very cumbersome if you have a lot of subclasses.

But let's say we had a virtual type called type_of_this, which, even in the base class, always refers to the type of the top-most (most-derived) class. Then we could define operator= like this:

type_of_this& operator= (int num) {n=num;return *this;} and then the line (n=2).ExtraTask(); would work fine.

Notice that the use of type_of_this must be severely limited in order to maintain type-safety. A method whose return type is type_of_this cannot return any old MyInt. It must return a MyInt that is guaranteed to be of type type_of_this.

Admittedly, that example seems a little esoteric; (who uses the return value of operator= anyway?!) But in many cases I think it is more esthetically pleasing to mimic the behavior of the standard types as closely as possible.

Example 2: A list that can contain only one type

// A generic class for the entire list class List { public: struct ListItem {}; // an embedded class for the list items /*virtual*/ typedef ListItem Item; // always call the list item "Item" Item* FirstItem(); Item* NextItem (Item* item); void AddItem (Item* item); private: Item* firstItem; //... }; // A subclass for a specific type of list class MyList : public List { struct MyListItem : ListItem {int data;}; // list items contain additional data /*virtual*/ typedef MyListItem Item; // always call the list item "Item" }; main() { MyList list; list.FirstItem().data = 1; //Oops! Doesn't compile } In this example, we see a similar sort of problem. Every time we create a subclass of List, we would have to create new methods FirstItem and NextItem to return the correct type.

If there were such a thing as virtual typedef in C++, we could add the keyword virtual at the indicated places, and the return types and parameter types of FirstItem and NextItem would change automatically to the correct type.

Again, in the interests of type-safety, it is necessary to note some things:

  1. Virtual types can only be used as pointers or references, because the compiler cannot know the size of a virtual type in advance.
  2. The second virtual typedef must be a derived class of the first one; think of it as "updating" the type Item rather than hiding the old one, as a regular typedef would do.
  3. As with virtual methods, the second virtual is optional.
  4. Unlike ordinary typedefs, virtual typedefs are not synonyms: the virtual typedef Item has a very different meaning from the type ListItem.
  5. The keyword type_of_this is exactly the same as a virtual typedef, except that when you derive a subclass, it is "updated" automatically without you having to type anything.
  6. MyList::NextItem cannot usually be passed any old Item* as a parameter. Only one that is guaranteed to be compatible with the Item* of the most-derived class can be passed.

The rules are complicated for determining whether type-safety is guaranteed and I make no attempt to formulate them here. But to see the subtleties involved, consider this example:

MyList list1; list1.AddItem(new MyList::MyListItem); // legal MyList& list2 = someOtherList; list2.AddItem(new MyList::MyListItem); // not legal This is because we don't know if list2 really is a MyList or if it is a subclass of MyList, whereas list1 definitely is a MyList because we created it right there.

Note: It is tempting to try to extend the virtual typedef concept even further. Rather than defining a struct ListItem and then creating a virtual typedef, it would appear simpler to just declare the struct as being virtual, like this:

class List { virtual struct Item {/*...*/}; /*...*/ }; class MyList : public List { virtual struct Item {int data;} /*...*/ };

The problem with this is as follows: with virtual typedefs, you can only create pointers or references, because the size of the type is unknown. (This is because a derived class may have "updated" it.) But we will, in all probability, want to create objects of type MyListItem. That is why it is better to create a concrete type, and then make a virtual typedef of it.

But is this a problem? You can also unvirtualize methods by explicit referencing.

Last modified May 20, 2000
Back to the Computer stuff page