Base Class Aliases for The-C++-After-0x

Author: James Widman & Thorsten Ottosen
Contact: widman -> gimpel dot com & thorsten dot ottosen -> dezide dot com
Organizations: Gimpel Software & Dezide Aps
Date: 2009-04-28
Number: N2881=09-0071
Working Group: Evolution

Abstract

[NOTE! We strongly suggest that EWG ignore this proposal until after the FDIS for C++0x ships.]

This paper provides motivation and a design proposal for base class aliases. A base class alias simplifies the naming of a base class by enabling the use of the new alias-declaration syntax (see [dcl.typedef]) as part of the syntax for a base-specifier in order to define a typedef name for the base class right inside the base-specifier.

Table of Contents

Motivation

The motivation is simple: when the name of a base class is a template-id, repeating it becomes unpalatable. For example, after preprocessing, one popular implementation of std::bitset looks like this:

  template<size_t _Nb>
    class bitset
    : private _Base_bitset
        <
        ((_Nb) < 1
        ? 0
        : ((_Nb) + numeric_limits<unsigned long>::digits - 1)
            / numeric_limits<unsigned long>::digits)
        >
    {
    private:
      typedef _Base_bitset
            <
            ((_Nb) < 1
            ? 0
            : ((_Nb) + numeric_limits<unsigned long>::digits - 1)
                / numeric_limits<unsigned long>::digits)
            >
      _Base;
    // [...]
    };

We propose the introduction of a small pure language extension that enables the definition of a typedef name for a base class at the point of its base-specifier . The syntax would echo both

Naturally, a base class alias name would be visible to name lookup immediately after its base-specifier (so that it could be used in subsequent base-specifiers). But there is a slight twist: since it's a very common practice to define a typedef name for a base class as a member of the derived class, we propose that each base alias name be implicitly injected as a member typedef name within the scope of the derived class (just as if the user had written the typedef there, and right at the point where the injected-class-name is injected).

Example: With base class aliases, the definition of bitset could be rewritten to the following (somewhat-less appalling) version:

  template<size_t _Nb>
    class bitset
    : _Base = private _Base_bitset
        <
        ((_Nb) < 1
        ? 0
        : ((_Nb) + numeric_limits<unsigned long>::digits - 1)
            / numeric_limits<unsigned long>::digits)
        >
    // '_Base' is now in scope here
    {
    // The name '_Base' is also  injected here
    // as a private typedef name (along
    // with the injected-class-name).

    // [...]
    };

Here is another example from a fairly simple Boost library

template
<
    class T,
    class CloneAllocator,
    class Allocator     
>
class ptr_vector :
    public ptr_sequence_adapter< T,
                                std::vector<void*,Allocator>,
                                CloneAllocator >
{
    typedef ptr_sequence_adapter< T,
                                  std::vector<void*,Allocator>,
                                  CloneAllocator >
        base_class; 

Again, the length of the template-id naming the base class is an enemy of readability and maintainability. With base class aliases we could rewrite the example as:

template
<
    class T,
    class CloneAllocator,
    class Allocator
>
class ptr_vector : base_class = 
    public ptr_sequence_adapter< T,
                                std::vector<void*,Allocator>,
                                CloneAllocator >
{

The benefits are compounded when one base-specifier depends on another. Example:

template< class T, class U, class V >
class Foo : base1 = public some_base<T,U,V>, 
            base2 = public another_base<base1,T,U,V>,
            // ...
{

We can summarise the advantages of this feature as follows:

  1. it makes it easier to write code involving base classes
  2. it makes it easier to maintain code involving base classes
  3. it generalizes the use of the super keyword often seen in languages with single inheritance
  4. it makes the language more accessible to novices and experts alike

Implementability

We believe the proposal is straightforward to implement.

Impact

We are not yet aware of any novel adverse effects that would be invited by this proposal. Indeed, there should be few or none because it merely appends to the list of syntactic contexts where a typedef name can be introduced into some scope.

Design considerations

The access of the injectecd base alias name is an open issue. Consider the following example

class Foo : base1 = public    some_base,
            base2 = protected another_base,
            base3 = private   yet_another_base
{}; 

typedef Foo::base1 base1; // OK?
typedef Foo::base3 base3; // ERROR, 'base3' is a private name

class Bar : public Foo
{
    typedef base2 foos_2nd_base; // OK?
};

We suspect that the syntax should enable the user to explicitly indicate the access of the injected typedef name:

class Foo :  public    base1 = public    some_base,
             protected base2 = protected another_base,
             private   base3 = private   yet_another_base
{}; 

typedef Foo::base1 base1; // Ok, base1 is a public membr of Foo.
typedef Foo::base3 base3; // ERROR, 'base3' is a private name

class Bar : public Foo
{
    typedef base2 foos_2nd_base; // Ok, base2 is a protected 
                                 // member of Foo.
};

This leaves us with one open question: when the alias definition does not have an access-specifier, what access do we assign to it? What's the "sensible default"? Here are some possible answers to that question: