Topic: Proposal: static extensions to built-in types
Author: phalpern@truffle.ma.ultranet.com (Pablo Halpern)
Date: 1998/04/29 Raw View
John Hancock <jhancock+@IUS5.IUS.cs.cmu.edu> wrote:
> And this change to the language would break no
>existing code, since char::foo would be illegal in today's standard.
char::foo; defines a global variable, foo, of type char.
I have to look at your post more carefully before I can comment on the
substance of it.
-------------------------------------------------------------
Pablo Halpern phalpern@truffle.ultranet.com
I am self-employed. Therefore, my opinions *do* represent
those of my employer.
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: John Hancock <jhancock+@IUS5.IUS.cs.cmu.edu>
Date: 1998/04/25 Raw View
Short version:
-------------
There are a number of problems that arise in C++ template
design because the built-in types cannot be extended as classes can.
I propose that C++ needs a mechanism to extend the built-in types
in a static manner, i.e. extensions would only be to the type as a
whole. Static extensions would not change the built-in behavior of
the type as they could not override existing built-in operators.
In particular, static extensions to the built-in types would provide
a better solution than trait classes for many template designs.
Long version:
------------
A few days ago I posted something about problems with
partial specialization of trait classes. In thinking about
the problem, I've come to believe that the trait class
mechanism is inherently flawed... Perhaps there have been
others who have proposed something similar, but I hope the
following illustrates a need for extending the built-in types
in a limited fashion that wouldn't break existing code.
When designing software using templates, there are a number
of problems that arise when designing template classes and
functions that work well with both built-in types and
user-defined classes. This is because the built-in types
cannot be extended. This problem needs to be addressed.
One solution to the problem of "extending" the built-in types has
been to store information in trait classes (such as numeric_traits
in the standard library). However, trait classes are seriously
limited. Trait classes must be specialized for each type individually
and cannot take advantage of relations between class hierarchies or
implicit casts.
First, let's give an example of where a trait class or similar
construct might be useful. Let's say I have a function:
(ignoring the return type presently, call it void)
void accumulate(const Array<T> &a)
{
T val;
for (int i=0;i<a.size();++i)
val += a[i];
}
If T is char, val may quickly overflow. Declaring val as a double
would solve this problem, but what happens if T is Array<char>
(assume Array<T> has the necessary operators defined)? We then
want val to be type Array<double>. This type promotion problem
is typically solved with a trait class, i.e.
template <class T>
PrecisionType
typedef double Type;
};
so that the default precision type is a double. Now we want
to specialize it so that it would work for an entire Array<T>.
We'd like to then say:
template <class T>
PrecisionType<Array<T> >
typedef typename Array<PrecisionType<T> > Type;
};
Unfortunately, the syntax of C++ doesn't allow this, since
"template <class T> PrecisionType ..." looks like a redefinition
of PrecisionType rather than a specialization. Instead,
we'd have to specialize PrecisionType explicitly for Array<char>,
Array<int>, Array<double>, Array<Array<char> >, etc.
An alternative and better solution could be used if C++ treated
the built-in types a bit more like classes. Users should be able
to define specific typedefs and static members (including
functions) of "class" char, double, etc. This would allow traits
to be made members of a class rather than put in external trait
classes. We limit extensions to "class" char to be static so
that we can't change the behavior of an individual char, float,
etc.
Static class members provide a much better solution than trait classes
for a variety of problems. If static members of built-in types were
subject to namespaces, potential name collision could essentially be
eliminated. And this change to the language would break no
existing code, since char::foo would be illegal in today's standard.
With a little extra smarts in the compiler, we could declare
(maybe with a somewhat different syntax -- a new keyword 'builtin'?)
class char{ //extending built-in char
public:
typedef double PrecisionType;
};
For our array class we could write the following:
template <class T>
class Array{
public:
typedef Array<T::PrecisionType> PrecisionType;
};
Array<char>::PrecisionType would then map to Array<char::PrecisionType>
which is Array<double> (the desired answer). This is a much cleaner
solution. It provides better syntax, better encapsulation, and can
take advantage of class hierarchies. Currently, we just can't do it
because of the limitations of the built-in types.
With this change, the numeric_traits class (or each of its members
individually) in the standard library could instead be made a member
of char, int, etc. Now we would use:
T::numeric_traits.max (or perhaps T::max) instead of numeric_traits<T>.max
By taking advantage of class hierarchies, we can do the following:
class A{
public:
static numeric_traits_class numeric_traits;
....
};
class B : public A{
};
This way, numeric_traits is inherited from class A. With the
template-based trait class, we have to specialize both
numeric_traits<A> and numeric_traits<B> individually.
Even more insidious problems arise with trait class specialization
if we define a class
template <class T, unsigned int N> Array{
.... };
where N is the size of the Array.
How can we possibly specialize a trait class for every possible
template instantiation of Array? There are an infinite number
of possibilities for N (OK, not infinite, only 4.3 billion for
a 4-byte integer), even if there are not many for any individual
application.
Some might suggest that rather than changing the language,
one should make a new class 'myfloat' which would have the
additional static functions, typedefs, etc. in addition to
behaving like the built-in float. Unfortunately, a new class
(to my knowledge) can never behave exactly like the built-in type
because only a single implicit cast is allowed. No matter how carefully
the class is designed it will fail in undesirable ways somewhere.
Even to work in most of the expected cases, 'myfloat' would have to
define all of the built-in operators for myfloat, which ought to be
unnecessary. In addition, having a new class like this would
irritate users of such a library, and could result in a different
'myfloat' class for every different library -- irritating and
ridiculous indeed. Finally, as a library developer, there's no
guarantee that a user would use the provided new 'myfloat' class
instead of 'float'. A static addition to the built-in type, however,
would be transparent to the user since the additions need only
be referred to within library code.
I hope the advantages of a member trait versus an external
trait class are apparent (at least for this example -- there
are probably other cases where an actual trait class is needed).
There may be the slight disadvantage of adding some complication
to the compiler, but I think this is minor compared to the advantages.
Any chance something like this could be put into the C++ standard?
John
--
--------------------------------------------------------------------------
John A. Hancock, Robotics Institute, Carnegie Mellon University
jhancock@ri.cmu.edu, http://www.ius.cs.cmu.edu/~jhancock/
"He who laughs has not yet heard the bad news." -- Bertolt Brecht
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]