The Casting Vote

by Sean A. Corfield

I'm writing this on the flight from San Francisco to Chicago after the most recent C++ meeting in Monterey, CA. It's been an eventful week - see Francis' Diary of an Observer - and many problems with the draft have been resolved. In Overload 8, I indicated that the ANSI public review had begun and that other countries would also be soliciting comments. Some of those comments were available in Monterey, but I think many more are yet to come: the public review period has been extended due to an unexpected one month slip in the ballot process. This will give people more time to read the draft and comment on it - keep those comments coming in!

Two years and counting...

We've just about reached the point now where 'all' we have left to do is resolve the 'small' issues that keep cropping up. There are no major extensions on the table, no major library additions planned and no major language changes predicted. After a period of rapid and widespread change, the draft standard is finally stabilising. Whilst that may give C++ programmers (and their managers) cause for rejoicing, it doesn't mean the committee's work is nearly done! The flow of small issues means that it will probably take us until 1997 to arrive at a draft standard that is precise enough to submit as a Draft International Standard (see previous Casting Vote columns for details of the ballot process).

That means that the UK meeting in July '97 may well be the one at which we know whether or not we will be on the brink of an official ISO C++ Standard.

Monterey was the first meeting since the Extensions WG was disbanded. Some of the former EWG members joined the Library WG (including Bjarne Stroustrup) and the rest joined the pool of Core WGs. I spent Monterey with Core III which looked at templates, exceptions and namespaces - Core III is the "not-the-Extensions WG" - but probably the most important step forward was taken by Core I at this meeting.

Just one definition!

The biggest definitional hole in the draft has now been filled: the committee adopted wording that specifies what has become known as the One Definition Rule. The essence of this rule is that it is OK to have two definitions of something in different translation units if those definitions are 'the same'. For the purposes of the ODR, 'the same' means the token sequence is the same and the name binding of those tokens is the same in each translation unit. Whilst most of the effects of the ODR are 'obvious' and common sense, there are a couple of 'gotchas'. My understanding is that an inline member function that calls a (static) inline function will violate the ODR if defined in more than one translation unit:

// file.h
inline int max(int a, int b)
{
	return a > b ? a : b;
}
class A
{
public:
	// ...
	int biggest() const
	{ return max(x, y); }
private:
	int	x, y;
};

The member function big() has external linkage (because it is a member function) but it calls max() which has internal linkage and is therefore considered 'different' in each translation unit that includes file.h. I may be mistaken - I am writing this after hearing the discussion of the ODR proposal but before seeing the actual wording in the working paper.

To specialise or not to specialise

The closest thing to an extension that was added in Monterey was a clarification of the syntax for declaring and defining specialisations of templates. Now that partial specialisations have been adopted (see The Casting Vote in Overload 7), full specialisations were the 'odd one out' in the template world because they didn't start with the keyword template. In addition, static data members could only be specialised as definitions because the syntax did not allow you to distinguish between specialised declarations and definitions. This has been addressed by requiring specialisations to be declared (and defined) with the prefix template<>.

template<class T, class U> class A;
	// primary template
template<class V> class A<V*,int>;
	// partial specialisation of A
template<> class A<void*,int>;
	// full specialisation of A
A<int*,int*>*	app;
	// use primary template:
	// T==int*, U==int*
A<int*,int>*	api;
	// use partial specialisation:
	// V==int*
A<void*,int>*	avi;
	// use full specialisation

If you don't like this, blame me because it was my proposal and I've been lobbying for it for quite some time!

Are you pointing at me?

One of the template classes in the draft standard library which has attracted quite a few comments is the auto_ptr class, which allows you to wrap pointers so that they become exception-safe (or, at least, exception-safer). One of the members of auto_ptr is operator-> and I have had some mail from people who've tried this class and found it doesn't compile - see Q&A in this issue. The committee previously decided that the return type of operator-> should not be checked inside the declaration of a template so you could have auto_ptr<int> and not get a compile-time error for int* operator-> unless you tried to use it. I proposed that this relaxation be extended, because it is perfectly reasonable to call the operator explicitly as a function:

X x;
T* p = x.operator->();

This is valid even if T has no members. It's valid because you are not trying to dereference the type returned by operator->. The committee accepted my proposal and two paragraphs of the draft standard were removed as a result - definitely a step in the right direction!

Related to this, and part of the above proposal, the standard iterators in the draft library are now required to support i->m if it makes sense to do so. That will hopefully tidy up a lot of code that currently has to use (*i).m instead. I ended up editing the changes into the appropriate library clause and it made me realise just how much attention that section of the draft still needs: we're getting a lot of comments about the language clauses but it would be really helpful if you all tried to read the library and comment on that!

Except for destruction...

Over the last few meetings, the committee fixed a lot of the holes concerning exception-safety, by adding try/catch blocks around mem-initializers (well, around whole function bodies, in fact), providing auto_ptr and tightening up the rules about exception-specifications. This still left one particularly thorny problem: when an exception is thrown, the stack unwinds and destructors are called - if one of those destructors throws an exception, the program terminates (it actually calls terminate() which can be overridden). Quite a few people have called for some mechanism that allows a destructor to ask "can I throw an exception?" A proposal from Germany provided the solution: add a function, called uncaught_exception(), that returns true if an exception has been thrown but not yet caught (i.e., during stack unwinding). This provides the bare minimum necessary for robust handling of exceptions during destruction.

Synthesis

Core III also tidied up an important flaw in the semantics of namespaces. One of the benefits claimed for namespaces was that you could synthesise a new namespace from several others:

// standard namespace to be used by all
// programs written within ACME
namespace ACME {
  // open the standard library namespace:
  using namespace std;
  // open Rogue Wave's library namespace:
  using namespace RogueWave;
  // open version 3 of ACME's 'K' library:
  using namespace KLibV3;
}

The intent was that ACME's programs could then include the appropriate headers and just say:

using namespace ACME;

This worked, but there are times when you don't want to open the whole namespace, you only want to pull parts of it out without getting (potentially) everything. The obvious way to do that is with an explicitly qualified name without worrying which namespace the declaration really inhabits:

ACME::initialiseKLib();
ACME::list<ACME::widget> widgets;

Unfortunately, this didn't work! Bjarne Stroustrup proposed a change to allow qualified name lookup to 'tunnel' through using-directives which fixes this problem. The committee accepted the proposal so namespaces now fulfil their initial promises. It's taken a long time to get clarification on the meaning of namespaces and, even now, a lot of people still don't really understand how they work. At least we now have a stable base, that seems to work, on which to build.

Small is beautiful?

There were a large number of 'small' issues resolved in Monterey. Each WG has a list of outstanding issues for the clauses for which they are responsible. Those lists typically have fifty to a hundred issues active with WG members working hard to suggest resolutions, draft WP changes and get the committee to accept them. This process is generally fairly successful and will be the pattern of work for the committee for the next few years. Not all the resolutions are entirely sensible and here are two from Monterey that I think are somewhat dubious:

Boolean arithmetic?

Assignment operators now allow the left hand operand to be bool which allows you to write:

bool b = true;
b *= 42;

This wasn't universally popular with the committee with a quarter voting against, but it falls naturally out of the existing rules for bool, unfortunately, because those weren't strict enough in the first place.

First class rights for unions!

A union can no longer have members with reference type. It was argued that you can't do much with such things so we should ban them. This motion was particularly unfortunate, in that we have already voted on it and defeated it. It succeeded this time because two of the National Bodies that strongly objected were not represented at Monterey.

I should point out that the remaining small issue resolutions were reasonable and included such things as:

Name injection revisited

In Overload 7 I hinted that the committee were trying to restrict name injection to make it less surprising. In fact, at Monterey, there was a groundswell of support for removing the feature altogether but a couple of things stopped us. Consider the following code based on an example in Barton & Nackman:

template<typename T>
struct Comparable
{
friend bool operator==(const T&, const T&);
};
template<typename U>
struct Array
: struct Comparable< Array<T> >
{ ... };

Or consider this, simpler, example:

template<typename T>
class basic_complex
{
friend basic_complex<T>
	operator+(const basic_complex<T>&,
		const basic_complex<T>&);
// ...
};
basic_complex<double> z = 1.0;
z = 2 + z;

The last line requires a conversion on the left hand side of the + which means that operator+ must be a non-member function. It also means that operator+ cannot be a global template operator because conversions are not allowed there either (because of type deduction). So we must use a non-member, non-template operator which can be declared as needed for any sort of basic_complex. Only name injection allows us to do this.

At the moment then, it seems that name injection must live on. Steve Rumsby (maintainer of the UK C++ information web site) suggested three rules that might make name injection better behaved:

  1. inject into the namespace of the template definition, not the namespace of the use,
  2. defer injection to the end of a full expression (i.e., where temporaries are destroyed),
  3. if name injection occurs, reconsider the expression and if any names have changed their meaning, the expression has undefined behaviour.

These suggested rules are currently being discussed by the committee so we shall probably see a proposal on paper for voting at Tokyo. Incidentally, rule 1 only works since we changed the operator lookup rules in Austin.

What about that Barton & Nackman code? It factors out the name injection into a template base class so that any other class can be 'Comparable' - i.e., have an appropriate non-member, non-template equality operator - simply by deriving from Comparable. Neat? Clever? Obscure? I think we can expect to see much more of this sort of thing as programmers become more comfortable with OO design and flexible ways of using templates and inheritance - Barton & Nackman makes a good read on those grounds.

The future

For the committee, the future holds several more meetings at which we will continue to deal with small issues. For the C++ community, the future should hold an increasingly stable draft standard and compilers that conform more closely. Remember: two years and counting!