Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Question about base types

2 views
Skip to first unread message

Matthew Heaney

unread,
Jan 26, 1997, 3:00:00 AM1/26/97
to

I have a question about the relationship between types, first named
subtypes, and base types.

If I declare an integer type, say

type T is range 1 .. 10;

Then I think that according to the Ada model this means I'm declaring some
unnamed type, but whose first named subtype is T:

type <anonomous parent of T> is System.Min_Int .. System.Max_Int;
subtype T is <anonomous parent of T> range 1 .. 10;

Do I have this correct?

Now what is the relationship of T'Base to this model? Is T'Base the name
of the anonomous parent type

type T'Base is System.Min_Int .. System.Max_Int;
subtype T is T'Base range 1 .. 10;

Did the "anonmous parent" go away in Ada 95? Or is it still

type <anon> is range System.Min_Int .. System.Max_Int;
subtype T is <anon> range 1 .. 10;

and T'Base simply "refers" to this anonomous type?

I can declare objects of type T'Base, right?

O : T'Base;

Is this declaration the same as

O : <anonomous parent of T>;

Enquiring minds want to know...

--------------------------------------------------------------------
Matthew Heaney
Software Development Consultant
<mailto:matthew...@acm.org>
(818) 985-1271

Mats Weber

unread,
Jan 27, 1997, 3:00:00 AM1/27/97
to

> I have a question about the relationship between types, first named
> subtypes, and base types.
>
> If I declare an integer type, say
>
> type T is range 1 .. 10;
>
> Then I think that according to the Ada model this means I'm declaring some
> unnamed type, but whose first named subtype is T:
>
> type <anonomous parent of T> is System.Min_Int .. System.Max_Int;
> subtype T is <anonomous parent of T> range 1 .. 10;
>
> Do I have this correct?

No, but almost. the first line is wrong and should read

type <anonomous parent of T> is range <min> .. <max>;

where <min> and <max> are anonymous static implementation defined
constants that depend on the range you give in your type declaration.
For 1 .. 10, the compiler is quite likely to choose <min> = -128 and
<max> = 127 in order to store your type in a single byte.

> Now what is the relationship of T'Base to this model? Is T'Base the name
> of the anonomous parent type

Yes

> type T'Base is System.Min_Int .. System.Max_Int;
> subtype T is T'Base range 1 .. 10;
>
> Did the "anonmous parent" go away in Ada 95? Or is it still

The mechanism has changed a little, but the effect remains basically the
same.

> I can declare objects of type T'Base, right?

You can in Ada 95, but not in Ada 83.

> O : T'Base;
>
> Is this declaration the same as
>
> O : <anonomous parent of T>;
>
> Enquiring minds want to know...

This cannot be answered because the second declaration is illegal :-).

Robert A Duff

unread,
Jan 27, 1997, 3:00:00 AM1/27/97
to

In article <mheaney-ya0232800...@news.ni.net>,

Matthew Heaney <mhe...@ni.net> wrote:
>
>I have a question about the relationship between types, first named
>subtypes, and base types.

Good question. This terminology is pretty messed up in Ada. Ada 95
cleans it up a little bit, but it's still pretty awful. Sigh.

>If I declare an integer type, say
>
>type T is range 1 .. 10;
>
>Then I think that according to the Ada model this means I'm declaring some
>unnamed type, but whose first named subtype is T:

That's right (except it's called the "first subtype" in Ada 95). ALL
types are unnamed. You can never refer directly to a type in an Ada
program. You can refer to the first subtype, or the base subtype, or
some other subtype. These are all subtypes of the same underlying type.
We call that the "type of" the subtypes.

>type <anonomous parent of T> is System.Min_Int .. System.Max_Int;
>subtype T is <anonomous parent of T> range 1 .. 10;
>
>Do I have this correct?

Not exactly. The above declaration declares a type. Like all types, it
has no name. It has a "base range", which is chosen by the
implementation. The base range is NOT necessarily System.Min_Int
.. System.Max_Int. The RM requires that the base range include all the
values in 1..10, and that it be symmetric about zero (or be symmetric
plus one extra negative value). So the compiler could choose -10..10 as
the base range, or -11..10, or -1000..1000, or -1001..1000, etc. The
expectation is that the compiler will choose something efficient, so
it's usually -2**15..2**15-1, or -2**31..2**31-1, or something like
that. The base range determines when overflow happens -- that is, the
range used for intermediate results. In particular, if the correct
answer is in the base range, then the result of the expression is that
correct answer. Otherwise, the result is either the correct answer, or
you get an overflow (and Constraint_Error is raised).

(Note the difference between constraint checking and overflow checking
-- constraint checking is done on assignment, parameter passing, etc.
Overflow checking has to do with arithmetic operations. Constraint
checking is often/usually done in software (using additional compare
instructions or whatever), whereas overflow checking is normally done by
the hardware (i.e. the add instruction automatically traps on overflow,
or at least automatically sets an overflow flag that can be checked in
one "trap on overflow" instruction).)

The "base subtype" of this type, called T'Base, is an unconstrained
subtype whose bounds are the base range. The "first subtype" of this
type is called T, and it is constrained to 1..10. Suppose the base
range happens to be -2**31..2**31-1 (which is allowed, since it includes
1..10, and is symmetric about zero, except for the extra -2**31 value).
Then:

X: T := 10;
Y: T'Base := 1_000_000_000;

If you assign the value 11 to X (as in X := X + 1;), there is a
constraint check, so Constraint_Error will be raised. If you assign Y
:= Y*Y, there is no Constraint_Check. However, they may be an overflow
check on the expression "Y*Y", so Y will either contain the value
1_000_000_000_000_000_000, or you will get Constraint_Error. Most
likely the latter.

>Now what is the relationship of T'Base to this model? Is T'Base the name
>of the anonomous parent type

No, T'Base is the name of an unconstrained subtype of the type.
Unconstrained means constraint checks are not done, but overflow checks
might be done. In most compilers overflow checks *are* done, most of
the time, because otherwise the compiler would have to implement
arbitrary-sized integers in order to store the result. The base subtype
T'Base still has a range associated with it -- namely, the base range
chosen by the compiler. (And there are cases where it is more efficient
to get the right answer than to do the overflow check -- e.g. in
floating point, where the hardware might do a calculation in a
double-precision register.)

>type T'Base is System.Min_Int .. System.Max_Int;
>subtype T is T'Base range 1 .. 10;
>
>Did the "anonmous parent" go away in Ada 95?

Yes.

>...Or is it still


>
>type <anon> is range System.Min_Int .. System.Max_Int;
>subtype T is <anon> range 1 .. 10;

No. (And this isn't quite right for Ada 83, either.)

>and T'Base simply "refers" to this anonomous type?
>

>I can declare objects of type T'Base, right?
>

>O : T'Base;

Yes.

>Is this declaration the same as
>
>O : <anonomous parent of T>;

Well, not exactly, but you won't go too far wrong if you think of it
that way. Just remember that the *compiler* chooses the base range, and
it is not usually Min_Int..Max_Int.

The anonymous type declared by "type T is range 1..10;" is considered to
be derived from root_integer, in the sense that this type belongs to the
class of all integer types. However, this isn't the same sort of
derivation that happens when you write an explicit derived type decl --
in particular, the base range is not inherited from root_integer, but is
chosen by the compiler. (This model is different from the Ada 83 model,
where the type of T is considered to be derived from some integer type
in Standard. This doesn't make much difference in the behavior of
programs, but it does mean that compilers can support large ranges
without having any large-ranged types in Standard.)

To write strictly portable code, you have to make sure that every
expression result (of type T) is in the range -10..10. If you want to
make sure all variables are in the range 1..10, but you want to allow
expression results in the range -1000..1000, then you should say:

type Dummy is range -1000..1000;
subtype T is Dummy range 1..10;

which guarantees that T'Base includes -1000..1000, and possibly more.
Then declare your variables to be of subtype T.

Note that the RM uses phrases like "the type Standard.Integer".
Strictly speaking, this is nonsense, since the name Standard.Integer
denotes a subtype, not a type. The above wording is really a shorthand
for "the (unnamed) type that underlies the subtype Standard.Integer", or
"the (unnamed) type of the subtype Standard.Integer".

One final confusion: The term "anonymous type" does not mean "a type
that has no name", since *all* types have no name. Instead, the term
"anonymous type" means "a type whose first subtype has no name". For
example, "task T is ..." declares an object of an anonymous task type --
the first subtype of that type has no name. (This explains why Ada 95
changed the term "first named subtype" to "first subtype" -- the first
subtype is not necessarily "named".)

For record types, and type extensions, 'Base is illegal. This helps
ensure that there are no values of the type that don't belong to the
first subtype of the type. For scalars, however, there can be values
outside the first subtype, and even values outside the base subtype.
(E.g., integer types represent the infinite range of mathematical
integers, most of which are outside the base range. This infinite range
is generally used for compile-time calculations of static expressions,
but at run time, overflow checks normally prevent the actual use of the
infinite range. Too bad -- see the recent language-war thread in which
several people have pointed out the advantages of having arbitrary
ranges at run time in Smalltalk.)

- Bob

Robert I. Eachus

unread,
Jan 28, 1997, 3:00:00 AM1/28/97
to

In article <mheaney-ya0232800...@news.ni.net> mhe...@ni.net (Matthew Heaney) writes:

> I have a question about the relationship between types, first named
> subtypes, and base types.

> If I declare an integer type, say

> type T is range 1 .. 10;

> Then I think that according to the Ada model this means I'm declaring some
> unnamed type, but whose first named subtype is T:

> type <anonomous parent of T> is System.Min_Int .. System.Max_Int;


> subtype T is <anonomous parent of T> range 1 .. 10;

> Do I have this correct?

No. The range of the anonomous type must be at least -15..15, but
the actual range is implementation dependent.

> Now what is the relationship of T'Base to this model? Is T'Base the name
> of the anonomous parent type

> type T'Base is System.Min_Int .. System.Max_Int;


> subtype T is T'Base range 1 .. 10;

There is no notation for declaring such an integer type in Ada. The
type has a 'First and 'Last, but it has NO range constraint.

> Did the "anonmous parent" go away in Ada 95? Or is it still

> type <anon> is range System.Min_Int .. System.Max_Int;
> subtype T is <anon> range 1 .. 10;

Arrggh! One of the worst bugs in the Ada 83 RM, long since fixed.
So yes, it is gone in Ada 95. (Well, not the anonymous type. The
idea that there is a possible "equivalent" declaration in Ada.)

> and T'Base simply "refers" to this anonomous type?

Technically T'Base is a subtype of the anonomous type which contains
no constraint:

subtype T'BASE is <anon>;

> I can declare objects of type T'Base, right?

> O : T'Base;

Yes, but please don't name them O.

> Is this declaration the same as

> O : <anonomous parent of T>;

Other than the fact that the second declaration is not legal in
Ada? Not much difference. Just remember that T'Base is a subtype of
the anonymous type which applies no constraint, and you are all set.

> Enquiring minds want to know...

Watch out, soon you may turn into a language lawyer.
--

Robert I. Eachus

with Standard_Disclaimer;
use Standard_Disclaimer;
function Message (Text: in Clever_Ideas) return Better_Ideas is...

Robert A Duff

unread,
Jan 28, 1997, 3:00:00 AM1/28/97
to

In article <32ECD6...@elca-matrix.ch>,

Mats Weber <Mats....@elca-matrix.ch> wrote:
>For 1 .. 10, the compiler is quite likely to choose <min> = -128 and
><max> = 127 in order to store your type in a single byte.

The compiler can choose -2**31..2**31-1 for the base range, and still
store variables of subtype T in a single byte. This makes sense if the
machine has a load instruction that takes a single byte from memory and
puts it in a 32-bit register.

- Bob

Mats Weber

unread,
Jan 28, 1997, 3:00:00 AM1/28/97
to

> > If I declare an integer type, say
>
> > type T is range 1 .. 10;
>
> > Then I think that according to the Ada model this means I'm declaring some
> > unnamed type, but whose first named subtype is T:
>
> > type <anonomous parent of T> is System.Min_Int .. System.Max_Int;
> > subtype T is <anonomous parent of T> range 1 .. 10;
>
> > Do I have this correct?
>
> No. The range of the anonomous type must be at least -15..15, but
> the actual range is implementation dependent.

I think this should be -10 .. 10. Nothing in the RM says it must be a
power of two minus one.

Robert Dewar

unread,
Jan 29, 1997, 3:00:00 AM1/29/97
to

Mats said

"No, but almost. the first line is wrong and should read

type <anonomous parent of T> is range <min> .. <max>;"


That's still wrong, or at least confusing, since it implies that the
anonymous parent of T is constrained, which is wrong.

The implicit declaration is more like

type <anonymous parent of T> is new predefined-integer-type

where predefined-integer-type is one of the predefined base types, e.g.
integer'base (NOT integer).

Note that in Ada 95, the base types of integer types are NOT constrained,
for example Integer'Base is not a constrained type. Implementations are
allowed to restrict the values of type Integer'Base that they handle, but
conceptually, Integer'Base includes all possible integer values.


Robert I. Eachus

unread,
Jan 29, 1997, 3:00:00 AM1/29/97
to

I said:

> No. The range of the anonomous type must be at least -15..15, but
> the actual range is implementation dependent.

In article <32EE14...@elca-matrix.ch> Mats Weber <Mats....@elca-matrix.ch> writes:

> I think this should be -10 .. 10. Nothing in the RM says it must be a
> power of two minus one.

Sigh, think of it as a ramification. ;-) You are correct that the
RM no longer requires that the range be based on a power of two, but
the rules for the attribute 'SIZE get mixed in here. Since T'SIZE is
in (integer) bits, it must be 4 or greater (see 13.3). Any range that
includes 1..10 is legal, including -5..10. But T'BASE'SIZE must be at
least 5, because of the rule about symmetric ranges. So there are at
least 31 possible values for an object of type T'BASE. (Thirty-one?
Yep, thirty-one. This is due to the dispensation for ones-complement
machines to allow for two zeros.)

Since the anonymous type is unconstrained and contains all the
legal values of an object of type T'BASE, that means that there must
be at least 31 possible values, again distributed symmetrically around
zero, but there can be more. And since the attribute is defined for
subtypes and objects, not types, it is possible for the size in bits
of an anonymous type to be log2(100) for example. So minus one
hundred to ninety-nine would be a possible range, but minus ten to ten
would not be.

Does this say that you can't put objects of (sub)type T in one
digit on a decimal machine? No. It says that objects of type T'BASE
sometimes require more space than objects of type T. This has always
been a known ramification in Ada 83. (In Ada 83 it only came up for
loop variables, in Ada 95 it can appear in lots of other places.)

Robert A Duff

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

In article <EACHUS.97J...@spectre.mitre.org>,

Robert I. Eachus <eac...@spectre.mitre.org> wrote:
>
> I said:
>
> > No. The range of the anonomous type must be at least -15..15, but
> > the actual range is implementation dependent.
>
>In article <32EE14...@elca-matrix.ch> Mats Weber <Mats....@elca-matrix.ch> writes:
>
> > I think this should be -10 .. 10. Nothing in the RM says it must be a
> > power of two minus one.

This is correct. If you say "type T is range 1..10;", then T'Base will
be -10..10, or some range including that. Period. In both Ada 83 and
Ada 95. For efficiency reasons, most compilers will choose a much
larger range, based on some power of 2, and 2's complement arithmetic.
But the only RM requirement here is that T'Base'Range be symmetric about
zero (plus perhaps one extra negative value), and include 1..10.

Sorry, but the stuff below is not correct.

> Sigh, think of it as a ramification. ;-) You are correct that the
>RM no longer requires that the range be based on a power of two, but
>the rules for the attribute 'SIZE get mixed in here. Since T'SIZE is
>in (integer) bits, it must be 4 or greater (see 13.3). Any range that
>includes 1..10 is legal, including -5..10. But T'BASE'SIZE must be at
>least 5, because of the rule about symmetric ranges. So there are at
>least 31 possible values for an object of type T'BASE. (Thirty-one?
>Yep, thirty-one. This is due to the dispensation for ones-complement
>machines to allow for two zeros.)

"no longer"? The rules are the same in Ada 83 and 95. There is not,
and never was, any requirement for power-of-2 base ranges.

The rules for 'Size do *not* "get mixed in here".

Assuming the SP annex is supported, then T'Size=4, and T'Base'Size=5.
See 13.3(55). But this has nothing whatsoever to do with the range of T
or T'Base.

Two zeros? There is no dispensation -- there's only one zero for
integers in Ada. If the machine is one's complement, and therefore has
two ways to represent zero, then the Ada compiler and/or hardware have
to make that fact invisible.

> Since the anonymous type is unconstrained and contains all the
>legal values of an object of type T'BASE, that means that there must
>be at least 31 possible values,

No -- see above.

>... again distributed symmetrically around


>zero, but there can be more. And since the attribute is defined for
>subtypes and objects, not types, it is possible for the size in bits
>of an anonymous type to be log2(100) for example. So minus one
>hundred to ninety-nine would be a possible range, but minus ten to ten
>would not be.

No. -10..10 is valid for T'Base'Range.

> Does this say that you can't put objects of (sub)type T in one
>digit on a decimal machine? No.

A better answer is "Who cares?"

>...It says that objects of type T'BASE


>sometimes require more space than objects of type T.

True.

>...This has always


>been a known ramification in Ada 83. (In Ada 83 it only came up for
>loop variables, in Ada 95 it can appear in lots of other places.)

True.

- Bob

Matthew Heaney

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

In article <E4oE3...@world.std.com>, bob...@world.std.com (Robert A
Duff) wrote:


>>type <anonomous parent of T> is System.Min_Int .. System.Max_Int;
>>subtype T is <anonomous parent of T> range 1 .. 10;
>>
>>Do I have this correct?
>
>Not exactly. The above declaration declares a type. Like all types, it
>has no name. It has a "base range", which is chosen by the
>implementation. The base range is NOT necessarily System.Min_Int
>.. System.Max_Int. The RM requires that the base range include all the
>values in 1..10, and that it be symmetric about zero (or be symmetric
>plus one extra negative value). So the compiler could choose -10..10 as
>the base range, or -11..10, or -1000..1000, or -1001..1000, etc. The
>expectation is that the compiler will choose something efficient, so
>it's usually -2**15..2**15-1, or -2**31..2**31-1, or something like
>that. The base range determines when overflow happens -- that is, the
>range used for intermediate results. In particular, if the correct
>answer is in the base range, then the result of the expression is that
>correct answer. Otherwise, the result is either the correct answer, or
>you get an overflow (and Constraint_Error is raised).

Can you point out to me where in the RM95 it states that the range of
T'Base must be symmetric around 0?

Can you point out to me where the RM95 states that the "portable range" of
T (our example here) need only be -10 .. 10?

This is sort of a bummer. I had hoped that T'Base would give me something
nearer to System.Max_Int. (But maybe you'll give me a reason why it won't
matter.)

Here's what I want to do. Suppose I have a floating point heading type,
and I want to override its primitive add operator so that it behaves like a
modulo number, as you'd expect headings to do:

type Heading is digits 6 range 0.0 .. 360.0;

function "+" (L, R : Heading) return Heading;

function "-" (L, R : Heading) return Heading;

So that

H : Heading := 359.0 + 2.0; -- H has the value 1.0

H : Heading := 1.0 - 2.0; -- H has the value 359.0

Can I do this implementation below, legally and portably?

function "+" (L, R : Heading) return Heading is
Sum : constant Heading'Base := L + R;
begin
if Sum > Heading'Last then
return Sum - Heading'Last;
else
return Sum;
end if;
end;

Will the assignment to Sum ever cause Constraint_Error?

I could generalize this to be a generic:

generic
type T is digits <>;
function Generic_Heading_Add (L, R : T) return T;

function Generic_Heading_Add (L, R : T) return T is
Sum : constant T'Base := L + R;
begin
if Sum > T'Last then
return Sum - T'Last;
else
return Sum;
end if;
end;

That way, I could use this any time I needed some sort of heading type

type Ownship_Heading is digits 4 range 0.0 .. 360.0;

function "+" is new Generic_Heading_Add (Ownship_Heading);

So that I get this special "+" as the primitive op.

(I could generalize the algorithm even more, so I could use it for
Longitude types, too.)

The question is: what range of T'Base case I assume? Am I guaranteed that
I won't get Constraint_Errors during the calculation of the sum? (Assume
the range of the actual type is 0 .. 360.)

Still confused...

matt

Robert Dewar

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

Matthew asks

Can you point out to me where the RM95 states that the "portable range" of
T (our example here) need only be -10 .. 10?

Obviously the base range need only cover the required range of the subtype
being declared. Where can you possibly get any other idea from the RM?

This is sort of a bummer. I had hoped that T'Base would give me something
nearer to System.Max_Int.

Of course not, it would be horribly inefficient on many machines to choose
System.Max_Int as the base type of all integer types. Consider the case of
GNAT where all implementations support at least 64-bits for Max_Int, but
of course on many architectures, 64-bit arithmetic is not available, and
has to be synthesized. You really do NOT want ordinary Integer arithmetic
to have to use inefficient 64-bit arithmetic!

If you want Max_Int, then use Max_Int!

What T'Base gives you is an efficient hardware type big enough to hold
your subtype.

Can I do this implementation below, legally and portably?

function "+" (L, R : Heading) return Heading is
Sum : constant Heading'Base := L + R;
begin
if Sum > Heading'Last then
return Sum - Heading'Last;
else
return Sum;
end if;
end;

No you can't, because there is no guarantee that the range of Heading'Base
exceeds the range of Heading. If you find this a "bummer" you have some
fundamental misconceptions about the type system of Ada.

What you need to do is to define a local subtype large enough to hold the
sum and do conversions appropriately.

A suggestion is to read up on how type declarations work in one of the
good Ada text books (Norman Cohen's book is a good choice if you want
something comprehensive). The RM is NOT a good source to learn basic
stuff like this, you really need to know Ada pretty well before you
can usefully use the RM as a reference source.


Matthew Heaney

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

In article <dewar.854626377@merv>, de...@merv.cs.nyu.edu (Robert Dewar) wrote:

>Can I do this implementation below, legally and portably?
>
>function "+" (L, R : Heading) return Heading is
> Sum : constant Heading'Base := L + R;
>begin
> if Sum > Heading'Last then
> return Sum - Heading'Last;
> else
> return Sum;
> end if;
>end;
>
> No you can't, because there is no guarantee that the range of Heading'Base
> exceeds the range of Heading. If you find this a "bummer" you have some
> fundamental misconceptions about the type system of Ada.

Yes, Robert, that is probably the case. Which is the reason for the post.

> What you need to do is to define a local subtype large enough to hold the
> sum and do conversions appropriately.

OK, I think I've got that part.

But here's another thing I'm confused about. During the panel discussion
on Friday morning at TRI-Ada '95, Alexandar Stepanov said that he couldn't
do iteration in Ada 83. What (I think) he wanted to do was to (actively,
not passively) iterate through an array, but to do that, your iteration
index has to fall off the end of the array (that's the termination
condition), the same way as it would it you were iterating through a linked
list (where the pointer would become null).

So I Alex was complaining that if you have (something like)

type AI is range 1 .. 10;
type AT is array (AI) of AC;

The index of an active iterator needs to have the range 1 .. 11, so Alex
said he didn't like Ada because he didn't have that range, and therefore
couldn't do (active) iteration.

So Tuck responded "That's what T'Base is for."

But it would seem that the only thing I can assume about T'Base is that it
has the same range as T, so what did Tuck mean? How would it be used to
solve Alex' problem?

So what is T'Base for, exactly? If, to guarantee a minimum range in which
to do intermediate calculations, I must explicitly declare a (sub) type
with that range, and then subtype off of that to get the type I really care
about, what does T'Base buy me? I can already do this in Ada 83, so why
was it added to the language, if I have to manually declare a supertype
anyway? It seems I can't assume anything about its range (just that it
includes T), so why would I ever declare an object of the type T'Base?

Matthew Heaney

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

In article <mheaney-ya0232800...@news.ni.net>, mhe...@ni.net
(Matthew Heaney) wrote:


>Here's what I want to do. Suppose I have a floating point heading type,
>and I want to override its primitive add operator so that it behaves like a
>modulo number, as you'd expect headings to do:
>
>type Heading is digits 6 range 0.0 .. 360.0;
>
>function "+" (L, R : Heading) return Heading;
>
>function "-" (L, R : Heading) return Heading;
>
>So that
>
>H : Heading := 359.0 + 2.0; -- H has the value 1.0
>
>H : Heading := 1.0 - 2.0; -- H has the value 359.0
>

>Can I do this implementation below, legally and portably?
>
>function "+" (L, R : Heading) return Heading is
> Sum : constant Heading'Base := L + R;
>begin
> if Sum > Heading'Last then
> return Sum - Heading'Last;
> else
> return Sum;
> end if;
>end;

I think I've got it!

My type declarations should have been

type Heading_Base is digits 6 range 0.0 .. 720.0;
subtype Heading is Heading_Base range 0.0 .. 360.0;

function "+" (L, R : Heading) return Heading is

Sum : Heading'Base := L + R; -- Sum has the range 0.0 .. 720.0

Just a quick little question: the "+" operator is a primitive operation of
Heading_Base, right? Can subtypes that are not first subtypes have
primitive operations (different from their parent's), or only first
subtypes? (Or should I have said ... only types?)

>generic
> type T is digits <>;
>function Generic_Heading_Add (L, R : T) return T;
>
>function Generic_Heading_Add (L, R : T) return T is

> Sum : constant T'Base := L + R;
>begin
> if Sum > T'Last then
> return Sum - T'Last;


> else
> return Sum;
> end if;
>end;

So this will work if I instantiate it on type Heading, because then T'Base
will have the range I need. So it's up to the instantiator to make sure
that T'Base has the required range. And of course the spec of the generic
should have a comment or something about what the required range of T'Base
is.

You know, it would be really swell if the language had a way of formally
stating in the spec (rather than as comments) what the range of T'Base
needs to be. Sound like another language you've heard of? Une langue qui
parle francias?

Robert A Duff

unread,
Jan 30, 1997, 3:00:00 AM1/30/97
to

In article <mheaney-ya0232800...@news.ni.net>,
Matthew Heaney <mhe...@ni.net> wrote:
>Can you point out to me where in the RM95 it states that the range of
>T'Base must be symmetric around 0?

3.5.4(9).

>Can you point out to me where the RM95 states that the "portable range" of
>T (our example here) need only be -10 .. 10?

-10..10 is the smallest range that satisfies the rules in 3.5.4(9).

>This is sort of a bummer. I had hoped that T'Base would give me something

>nearer to System.Max_Int. (But maybe you'll give me a reason why it won't
>matter.)

It's hard to imagine a compiler that would use anything less than
-2**7..2**2-7. But the minimum allowed by the RM is -10..10. If you


want more than that, just write:

type Dummy is range -1000..1000;
subtype T is Dummy range 1..10;

Then T'Base will include at least -1000..1000.

>Here's what I want to do. Suppose I have a floating point heading type,

We were talking about integers. Now you've switched to floats, which I
don't know much about. For floats, the rules are more complicated --
see 3.5.7. But in practise, I suspect compilers will choose base ranges
and precisions that match some hardware-provided type.

- Bob

Robert A Duff

unread,
Jan 31, 1997, 3:00:00 AM1/31/97
to

In article <mheaney-ya0232800...@news.ni.net>,
Matthew Heaney <mhe...@ni.net> wrote:
>type AI is range 1 .. 10;
>type AT is array (AI) of AC;
>
>The index of an active iterator needs to have the range 1 .. 11, so Alex
>said he didn't like Ada because he didn't have that range, and therefore
>couldn't do (active) iteration.
>
>So Tuck responded "That's what T'Base is for."
>
>But it would seem that the only thing I can assume about T'Base is that it
>has the same range as T, so what did Tuck mean? How would it be used to
>solve Alex' problem?

It's better to use 0..10, not 1..11, because 0 is guaranteed to be in
AI'Base. I've often written code like this (given the above):

subtype A_Count is AI'Base range 0..AI'Last;
Counter: A_Count := 0;

Then you can do things like:

X: AT;

while Counter < AI'Last loop
Counter := Counter + 1;
... X(Counter) ...
end loop;

Of course, if it's just a simple loop, then a 'for' loop, or perhaps a
loop with an exit statement, can avoid the need for the 0..10 range.

Of course, in Ada 83, it's not so bad either:

type Whatever_Count is range 0..10;
subtype Whatever_Index is Whatever_Count range 1..Whatever_Count'Last;

>So what is T'Base for, exactly? If, to guarantee a minimum range in which
>to do intermediate calculations, I must explicitly declare a (sub) type
>with that range, and then subtype off of that to get the type I really care
>about, what does T'Base buy me? I can already do this in Ada 83, so why
>was it added to the language, if I have to manually declare a supertype
>anyway? It seems I can't assume anything about its range (just that it
>includes T), so why would I ever declare an object of the type T'Base?

You can also assume that it is at least symmetric about zero.

What does it buy you? Well, sometimes it's more convenient to declare
the 0..10 subtype after the 1..10 subtype. I think it's even more
useful for floats -- check out the numerics annex for example, and the
Rationale for some explanation.

Here's another thing you can do with 'Base: Suppose I want to count some
apples, so I want to start at zero. I want to count at least a billion
of them, but that limit is fairly arbitrary -- I'll be perfectly happy
if I can count more than a billion of them.

type Dummy is range 0..1_000_000_000;
subtype Apple_Count is Dummy range 0..Dummy'Base'Last;
Number_Of_Apples: Apple_Count := ...;

Number_Of_Apples := Number_Of_Apples + 1;

Now, the compiler doesn't need to generate any constraint checking code
for the increment: it can't be less than zero, since we're adding 1, and
there's no need for the (useless) check against 1_000_000_000.
Presumably, the hardware will check (at least on many machines) against
'Base'Last, which is probably 2**31-1 on a 32-bit machine, but probably
2**35-1 on a 36-bit machine.

By the way, 'Base wasn't *added* in Ada 95 -- just some restrictions on
it were removed. And it's no longer allowed for composite types.

- Bob

Robert A Duff

unread,
Jan 31, 1997, 3:00:00 AM1/31/97
to

In article <mheaney-ya0232800...@news.ni.net>,
Matthew Heaney <mhe...@ni.net> wrote:
>Just a quick little question: the "+" operator is a primitive operation of
>Heading_Base, right? Can subtypes that are not first subtypes have
>primitive operations (different from their parent's), or only first
>subtypes? (Or should I have said ... only types?)

Only types have primitive ops. There's nothing stopping you from
declaring a "+" that takes a non-first subtype as parameters. If this
"+" is in the same package as the type, then it's primitive of the type.
If it's type-conformant with the predefined "+", then it will override
the predefined one.

- Bob

Robert Dewar

unread,
Jan 31, 1997, 3:00:00 AM1/31/97
to

iMatthew said

"But it would seem that the only thing I can assume about T'Base is that it
has the same range as T, so what did Tuck mean? How would it be used to
solve Alex' problem?"

But the programmer can control the range of T'base by deriving from an
appropraite base type!!!!!


Matthew Heaney

unread,
Feb 3, 1997, 3:00:00 AM2/3/97
to

In article <EACHUS.97...@spectre.mitre.org>,

eac...@spectre.mitre.org (Robert I. Eachus) wrote:


> > Can I do this implementation below, legally and portably?
>
> > function "+" (L, R : Heading) return Heading is
> > Sum : constant Heading'Base := L + R;
> > begin
> > if Sum > Heading'Last then
> > return Sum - Heading'Last;
> > else
> > return Sum;
> > end if;
> > end;
>

> You make one of the oldest mistakes in redefinition of operators.
>The declaration of Sum is recursive and will run you out of stack.

Ouch! Thanks for catching that.

Actually, this was a mental translation from a generic version

generic
type Heading is digits <>;
function Generic_Add (L, R : Heading) return Heading is
Sum := Heading'Base := L + R;
...

Now, some more questions!!

The predefined operators re-emerge in generics, right? Is it correct that
in the above example, the "+" operator is the predefined one for the
Heading type, even if the Heading type passed in has had its "+" operator
overridden?

For a generic, I can always get the overridden operator by explicitly
importing it:

generic
type F is digits <>;
with function "+" (L, R : F) return F is <>;
function Generic_Op (...);

So if I instantiated this generic with a floating point type whose "+" had
been overridden, then that overridden operator is what would get passed in?

> type Hidden is new Heading'Base range -360.0..360.0;
[cool example using derived types omitted]

> > The question is: what range of T'Base case I assume? Am I
> > guaranteed that I won't get Constraint_Errors during the
> > calculation of the sum? (Assume the range of the actual type is 0
> > .. 360.)
>

> If you write your generic based on the above you should be clean.
>Floating point base types have to be symmetric about zero.

It has been pointed out to me that to guarantee a base range - at least for
an integer type - I must explicitly declare a first-named subtype with the
base range I require, and then subtype off of that, ie

type Heading_Base is range 0 .. 720;
subtype Heading is Heading_Base range 0 .. 360;

This will guarantee that Heading'Base includes -720 .. 720. Is my
understanding correct here?

Next question: T'Base is the base subtype range for *all* subtypes of the
type, not just the immediate parent, right? For example,

type T is range 0 .. 100;
subtype U is T range 10 .. 90;
subtype V is U range 20 .. 80;

Then V'Base = U'Base = T'Base. Correct?

Next question: can the range for a subtype exceed the range of the subtype
mark used in its declaration? Can I do this

subtype U is T range 10 .. 90;
subtype Bigger_Than_U is U range 5 .. 95;

Is that legal?

Bob Duff (I think - perhaps it was you) gave an example using a subtype
that had a possibly larger range than its "parent."

type T is range 0 .. 100;
subtype U is T range 10 .. 90;
subtype Maybe_Bigger_Than_U is U range 0 .. U'Base'Last;

This seems rather odd to me: the range can be bigger? Is there something
special about using T'Base that allows you to exceed the range constraint
of the subtype mark used in the declaration?

Wouldn't this be more intuitive:

subtype Maybe_Bigger_Than_U is U'Base range 0 .. U'Base'Last;
-- (note the U'Base vs U)

Or is the rule that the subtype mark is used merely to identify the type of
the subtype, and its range constraints don't matter?

All this, and I haven't even gotten to the questions about floating point
types yet!

Given this:

type F is digits 6;

My understanding is that this is an unconstrained subtype of the type of F,
and no constraint checks apply - similar to 'Base applied to an integer
type. Right so far?

If so, then what's the difference between F and F'Base?

In my heading example, I can do this

type Heading_Base is digits 4 range 0.0 .. 720.0;


subtype Heading is Heading_Base range 0.0 .. 360.0;

but given this alternate specification of Heading_Base,

type Heading_Base is digits 4;


subtype Heading is Heading_Base range 0.0 .. 360.0;

would this be just as good?


Does Heading'Base guarantee only the range -360.0 .. 360.0, or is it
guaranteed to be the same as

type Heading_Base is digits 4; -- same as Heading'Base?

Wow! That's a lot of homework, Bob. I'll expect a quick response, got it?!

Thanks,
Matt

Robert I. Eachus

unread,
Feb 4, 1997, 3:00:00 AM2/4/97
to

In article <mheaney-ya0232800...@news.ni.net> mhe...@ni.net (Matthew Heaney) writes:

> Here's what I want to do. Suppose I have a floating point heading type,

> and I want to override its primitive add operator so that it behaves like a
> modulo number, as you'd expect headings to do:

-- OK...

> Can I do this implementation below, legally and portably?

> function "+" (L, R : Heading) return Heading is
> Sum : constant Heading'Base := L + R;
> begin
> if Sum > Heading'Last then
> return Sum - Heading'Last;
> else
> return Sum;
> end if;
> end;

You make one of the oldest mistakes in redefinition of operators.
The declaration of Sum is recursive and will run you out of stack.

Try:

type Hidden is new Heading'Base range -360.0..360.0;

function "+" (L, R : Heading) return Heading is
Sum : Hidden := (Hidden(L)-360.0) + Hidden(R);
begin
if Sum < 0.0
then return Heading(Hidden(L) + Hidden(R));


else return Sum;
end if;

end "+";

(Why do the computation twice? Because you don't want to lose
precision unnecessarily.) The type conversions should cause no
additional overhead.

> The question is: what range of T'Base case I assume? Am I
> guaranteed that I won't get Constraint_Errors during the
> calculation of the sum? (Assume the range of the actual type is 0
> .. 360.)

If you write your generic based on the above you should be clean.
Floating point base types have to be symmetric about zero.

--

Robert Dewar

unread,
Feb 4, 1997, 3:00:00 AM2/4/97
to

Robert Eachus said, commenting on a program by Matthew Heaney:

function "+" (L, R : Heading) return Heading is
Sum : constant Heading'Base := L + R;
begin
if Sum > Heading'Last then
return Sum - Heading'Last;
else
return Sum;
end if;
end;

"You make one of the oldest mistakes in redefinition of operators.
The declaration of Sum is recursive and will run you out of stack."

Robert replies

It is always a good idea to compile code before posting it. If you compile
the above function using GNAT, you will get:

1. procedure p is
2. type Heading is range 1 .. 10;
3.
4. function "+" (L, R : Heading) return Heading is
5. Sum : constant Heading'Base := L + R;
|
>>> warning: infinite recursion
>>> warning: Storage_Error will be raised at runtime

6. begin
7. if Sum > Heading'Last then
8. return Sum - Heading'Last;
9. else
10. return Sum;
11. end if;
12. end;
13. begin
14. null;
15. end;


Mats Weber

unread,
Feb 4, 1997, 3:00:00 AM2/4/97
to

> The predefined operators re-emerge in generics, right? Is it correct that

right.

> in the above example, the "+" operator is the predefined one for the
> Heading type, even if the Heading type passed in has had its "+" operator
> overridden?

right.

> For a generic, I can always get the overridden operator by explicitly
> importing it:
>
> generic
> type F is digits <>;
> with function "+" (L, R : F) return F is <>;
> function Generic_Op (...);

right.

> So if I instantiated this generic with a floating point type whose "+" had
> been overridden, then that overridden operator is what would get passed in?

yes.

> > type Hidden is new Heading'Base range -360.0..360.0;

> [cool example using derived types omitted]
>

> > > The question is: what range of T'Base case I assume? Am I
> > > guaranteed that I won't get Constraint_Errors during the
> > > calculation of the sum? (Assume the range of the actual type is 0
> > > .. 360.)
> >
> > If you write your generic based on the above you should be clean.
> >Floating point base types have to be symmetric about zero.
>

> It has been pointed out to me that to guarantee a base range - at least for
> an integer type - I must explicitly declare a first-named subtype with the
> base range I require, and then subtype off of that, ie

This is not necessarily mandatory: have a look at RM 3.5.7(10) which
talks about minimum ranges for base floating point types (the range
depends on whether or not you give a range constraint in your type
definition).

> type Heading_Base is range 0 .. 720;
> subtype Heading is Heading_Base range 0 .. 360;

this should be:

type Heading_Base is digits 4 range 0.0 .. 720.0;
subtype Heading is Heading_Base range 0.0 .. 360.0;

> This will guarantee that Heading'Base includes -720 .. 720. Is my
> understanding correct here?

Yes.

> Next question: T'Base is the base subtype range for *all* subtypes of the
> type, not just the immediate parent, right? For example,
>
> type T is range 0 .. 100;
> subtype U is T range 10 .. 90;
> subtype V is U range 20 .. 80;
>
> Then V'Base = U'Base = T'Base. Correct?

Yes.

> Next question: can the range for a subtype exceed the range of the subtype
> mark used in its declaration? Can I do this

No. The set of values of a subtype is a subset of the set of values of
its parent.

> subtype U is T range 10 .. 90;
> subtype Bigger_Than_U is U range 5 .. 95;
>
> Is that legal?

Yes, but it will raise Constraint_Error.

> Bob Duff (I think - perhaps it was you) gave an example using a subtype
> that had a possibly larger range than its "parent."
>
> type T is range 0 .. 100;
> subtype U is T range 10 .. 90;
> subtype Maybe_Bigger_Than_U is U range 0 .. U'Base'Last;

For this to work, the last line sould be changed to

subtype Maybe_Bigger_Than_U is U'Base range 0 .. U'Base'Last;

> This seems rather odd to me: the range can be bigger? Is there something


> special about using T'Base that allows you to exceed the range constraint
> of the subtype mark used in the declaration?

No.

> Wouldn't this be more intuitive:
>
> subtype Maybe_Bigger_Than_U is U'Base range 0 .. U'Base'Last;
> -- (note the U'Base vs U)

Yes.

> Or is the rule that the subtype mark is used merely to identify the type of
> the subtype, and its range constraints don't matter?

No. Not in this context. There are examples where the subtype's range is
ignored, e.g.

I : Integer;
P : Positive renames I;

...

P := -1; -- equivalent to I := -1: does not raise Constrint_Error.

> All this, and I haven't even gotten to the questions about floating point
> types yet!
>
> Given this:
>
> type F is digits 6;
>
> My understanding is that this is an unconstrained subtype of the type of F,
> and no constraint checks apply - similar to 'Base applied to an integer
> type. Right so far?
>
> If so, then what's the difference between F and F'Base?

I would say it's pretty small, but Bob Duff will know for sure :-)

> In my heading example, I can do this
>
> type Heading_Base is digits 4 range 0.0 .. 720.0;
> subtype Heading is Heading_Base range 0.0 .. 360.0;

I prefer this formulation because it is more explicit on the range you
really need in your calculations. You should also have a look at fixed
point types: for that kind of calculation (angles), I think they are
more appropriate than floating point (have fun :-).

> but given this alternate specification of Heading_Base,
>
> type Heading_Base is digits 4;
> subtype Heading is Heading_Base range 0.0 .. 360.0;
>
> would this be just as good?

digits 4 gives you a range of at least - 10.0 ** 4 * 4 .. + 10.0 ** 4 *
4, which is enough for what you need (see RM 3.5.7(10)).

> Does Heading'Base guarantee only the range -360.0 .. 360.0, or is it
> guaranteed to be the same as
>
> type Heading_Base is digits 4; -- same as Heading'Base?

Same, because Heading_Base'Base = Heading'Base, as Heading is a subtype
of Heading_Base.

--Mats

Jeff Carter

unread,
Feb 5, 1997, 3:00:00 AM2/5/97
to

I thought the main use of 'Base was intended to be something like:

generic -- Example
type Real is digits <>;
package Example is
function Op (Arg : Real) return Real;
end Example;

type Real is digits 7 range 0.0 .. 1.0;
package Actual is new Example (Real => Real);

The implementation of Op needs to be able to do calculations independent
of any range constraint on the actual parameter associated with the
formal parameter Real. So it does its calculations using Real'Base:

function Op (Arg : Real) return Real is
Result : Real'Base := Arg + 27.0;
begin -- Op
return Result / (Result + 1.0);
end Op;

See, for example, Generic_Elementary_Functions for such a use of 'Base.
I've taken a slightly different tack from G_E_F when I need a similar
effect:

generic -- Example
type Supplied_Real is digits <>;
package Example is
subtype Real is Supplied_Real'Base;

function Op (Arg : Real) return Real;
end Example;
--
Jeff Carter
Innovative Concepts, Inc.

Matthew Heaney

unread,
Feb 5, 1997, 3:00:00 AM2/5/97
to

In article <32F8C356...@innocon.com>, Jeff Carter
<car...@innocon.com> wrote:

>type Real is digits 7 range 0.0 .. 1.0;
>package Actual is new Example (Real => Real);

>The implementation of Op needs to be able to do calculations independent
>of any range constraint on the actual parameter associated with the
>formal parameter Real. So it does its calculations using Real'Base:
>
>function Op (Arg : Real) return Real is
> Result : Real'Base := Arg + 27.0;
>begin -- Op
> return Result / (Result + 1.0);
>end Op;

Yes, but the question is, What range can I assume Real'Base to have? I
don't know the rules for floating point types, but if it were like an
integer type, then the only thing we can assume is that Real'Base has the
range -1.0 .. 1.0.

I think (and I may be wrong here) that to be officially correct one must
subtype from an unconstrained real type:

type Real_Unconstrained is digits 7;
subtype Real is Real_Unconstrained range 0.0 .. 1.0;

package Real_Example is new Example (Real);

But I need to study that section of the RM some more - a lot more!

matt

Mats Weber

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

> >type Real is digits 7 range 0.0 .. 1.0;
> >package Actual is new Example (Real => Real);
>
> >The implementation of Op needs to be able to do calculations independent
> >of any range constraint on the actual parameter associated with the
> >formal parameter Real. So it does its calculations using Real'Base:
> >
> >function Op (Arg : Real) return Real is
> > Result : Real'Base := Arg + 27.0;

This could fail (i.e. raise Constraint_Error), since 27.0 is not
required to be in the range of Real'Base. But it will work on most
machines because on reasonable implementations, floating point types are
mapped to representations that the hardware can handle efficiently (e.g.
IEEE).

> >begin -- Op
> > return Result / (Result + 1.0);
> >end Op;
>
> Yes, but the question is, What range can I assume Real'Base to have? I
> don't know the rules for floating point types, but if it were like an
> integer type, then the only thing we can assume is that Real'Base has the
> range -1.0 .. 1.0.

Right. When a range is given in the type definition, the base range is
not required to have any additional values. If the declaration were

type Real_Base is digits 7;
subtype Real is Real_Base range 0.0 .. 1.0;

then Real'Base and Real_Base would have a range of at least
- 10.0 ** (4 * 7) .. + 10 ** (4 * 7) (see RM 3.5.7(10)).

> I think (and I may be wrong here) that to be officially correct one must
> subtype from an unconstrained real type:
>
> type Real_Unconstrained is digits 7;
> subtype Real is Real_Unconstrained range 0.0 .. 1.0;

No (see above).

> But I need to study that section of the RM some more - a lot more!

Maybe :-)

Jeff Carter

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

Mats Weber wrote:
>
> > >type Real is digits 7 range 0.0 .. 1.0;
> > >package Actual is new Example (Real => Real);
> >
> > >The implementation of Op needs to be able to do calculations independent
> > >of any range constraint on the actual parameter associated with the
> > >formal parameter Real. So it does its calculations using Real'Base:
> > >
> > >function Op (Arg : Real) return Real is
> > > Result : Real'Base := Arg + 27.0;
>
> This could fail (i.e. raise Constraint_Error), since 27.0 is not
> required to be in the range of Real'Base. But it will work on most
> machines because on reasonable implementations, floating point types are
> mapped to representations that the hardware can handle efficiently (e.g.
> IEEE).

Interesting. I was under the impression that Real'Base would be the same
as "digits 7" with no range constraint.


--
Jeff Carter
Innovative Concepts, Inc.

Now go away, or I shall taunt you a second time.

Robert A Duff

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

In article <mheaney-ya0236800...@news.ni.net>,

Matthew Heaney <mhe...@ni.net> wrote:
>Bob Duff (I think - perhaps it was you) gave an example using a subtype
>that had a possibly larger range than its "parent."
>
>type T is range 0 .. 100;
>subtype U is T range 10 .. 90;
>subtype Maybe_Bigger_Than_U is U range 0 .. U'Base'Last;

If I said that, I was wrong -- U should be U'Base.

I think somebody else answered your other questions.

- Bob

Robert I. Eachus

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

In article <dewar.855063927@merv> de...@merv.cs.nyu.edu (Robert Dewar) writes:

I said:

> "You make one of the oldest mistakes in redefinition of operators.
> The declaration of Sum is recursive and will run you out of stack."

Robert Dewar replies:

> It is always a good idea to compile code before posting it. If you compile

> the above function using GNAT, you will get...

> >>> warning: infinite recursion
> >>> warning: Storage_Error will be raised at runtime

This is a real nice warning, and don't think I'm complaining. But
an interesting question: does GNAT do the transformation in the
generated code, or just fall off the end of the stack? (In other
words replace calls to this "+" with raise Storage_Error.)

It would be a nice optimization--at least during development. But
in general I think it is not possible to do it correctly. It will
always be possible if the function is pure, has no side effects, and
only by value non-access parameters. Even then it should be done only
for a nested call with the same values as in the parent call. Can
this be extended? I don't think so. Imagine a nested program that
has no state and recursively exhausts the stack. Now an exception
handler for Storage_Error in the enclosing scope handles the exception
and computes the amount of stack used. Any of: passing a record by
reference, a parameter of an access type, or a side-effect would make
it possible for such a program to return useful results.

Robert Dewar

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

Mats Weber wrote:
<<
<< << type Real is digits 7 range 0.0 .. 1.0;
<< << package Actual is new Example (Real =<< Real);
<< <<
<< << The implementation of Op needs to be able to do calculations independent
<< << of any range constraint on the actual parameter associated with the
<< << formal parameter Real. So it does its calculations using Real'Base:
<< <<
<< << function Op (Arg : Real) return Real is
<< << Result : Real'Base := Arg + 27.0;
<<
<< This could fail (i.e. raise Constraint_Error), since 27.0 is not
<< required to be in the range of Real'Base. But it will work on most
<< machines because on reasonable implementations, floating point types are
<< mapped to representations that the hardware can handle efficiently (e.g.
<< IEEE). >>

This is a little misleading. While it is true that, unlike the case in
Ada 83, where of course the base type of type Real must have a big range,
the definitions of Ada 95 DO allow a narrow range IF that is what the
hardware provides (I am assuming annex G is implemented). Since there are
no machines with floating point types with 7 digits of precision and a
range that does not include 27.0, nor could there ever be such machines,
to worry about this eventuality is as silly as worrying about the fact
that a legal Ada implementation could raise storage_Error for every
possible program.

Robert Dewar

unread,
Feb 6, 1997, 3:00:00 AM2/6/97
to

<< This is a real nice warning, and don't think I'm complaining. But
an interesting question: does GNAT do the transformation in the
generated code, or just fall off the end of the stack? (In other
words replace calls to this "+" with raise Storage_Error.)

It would be a nice optimization--at least during development. But
in general I think it is not possible to do it correctly. It will
always be possible if the function is pure, has no side effects, and
only by value non-access parameters. Even then it should be done only
for a nested call with the same values as in the parent call.>>

(note, from now on I am using Norm's suggestoins of russian quotes << >>
for quoted text)

GNAT transforms the code to raise SE if and only if this is guaranteed to
be correct.


Norman H. Cohen

unread,
Feb 7, 1997, 3:00:00 AM2/7/97
to

Robert Dewar wrote:


> >>> warning: infinite recursion
> >>> warning: Storage_Error will be raised at runtime

This is very impressive!

So impressive, in fact, that I'm going to raise the bar: Do you issue
the same warning for unintentionally recursive controlled-type
operations? (For a controlled type CT, these would include a version of
Initialize that creates an uninitialized object of type CT, a version of
Adjust that assigns an object of type CT, or a version of Finalize in
which an object of type CT goes out of existence, either because it was
declared in finalize, because it was uncheckedly deallocated in
Finalize, or because it was created by an allocator for an access type
declared in Finalize.)

Such cases are especially hard to diagnose because the unintentional
recursive call is implicit, i.e., you won't find it in the program text.

--
Norman H. Cohen
mailto:nco...@watson.ibm.com
http://www.research.ibm.com/people/n/ncohen

Mats Weber

unread,
Feb 7, 1997, 3:00:00 AM2/7/97
to

True. But if you know that you are going to need larger intermediate
results, then I think it is wiser to declare

type Real_Base is digits 7;
subtype Real is Real_Base range 0.0 .. 1.0;

than

Robert Dewar

unread,
Feb 8, 1997, 3:00:00 AM2/8/97
to

Mats said

<<True. But if you know that you are going to need larger intermediate
results, then I think it is wiser to declare

type Real_Base is digits 7;
subtype Real is Real_Base range 0.0 .. 1.0;
>>

Mats, you are confused, the base type (and hence the range of intermediate
results) is always unconstrained, so that if you write:

subtype Real is digits 7 range 0.0 .. 1.0;

as in the original result, you will still get an unconstrained 7 digit
base type.

Robert Dewar

unread,
Feb 8, 1997, 3:00:00 AM2/8/97
to

Bob Duff said

"Good question. This terminology is pretty messed up in Ada. Ada 95
cleans it up a little bit, but it's still pretty awful. Sigh."

I actually do not agree that Ada 95 cleans it up. Yes, I know the
terminology in Ada 95 is more consistent, but it is also more at
odds with normal usage. The use of subtype for what everyone thinks
of as a type causes all sorts of confusion. For example, in Ada 95,
Integer is a subtype, and so is Integer'Base.

Yes, it is true that Ada 83 was a little confused in this area, but the
terminology chosen in Ada 95 is fundamentally confusing, and in fact I
note a couple of error messages in GNAT that are absolutely correct
but which always cause people trouble:

package x is .... -- oops forgot this hid "type" x

q : x;
|
Error: subtype name expected

Almost all programmers react to this message thinking, "why do I need a
subtype here, a type should be good enough"

The fact that

type x is ...

introduces the subtype x is bound to be confusing.

Sometimes we just decide to use the obvious terminology in GNAT instead
of the technically correct termiology, and we probably should do that
here too -- the message "type name expected
would be much clearer in this instance.

For example

subtype x is integer range 1 .. 10;
for x'size use 5;

GNAT says "rep clause cannot be given for subtype"


which is I think clear to people (at least no one ever asked for an
explanation of this message).

More accurate would be to say

"rep clause may only be given for first named subtype"

but this seems more confusing to us.

As Bob said, a bit of a mess, and as I say, in my opinion, definitely NOT
improved in Ada 95 -- I preferred the Ada 83 terminology here (actually
there are a number of instances where Ada 95 has changed Ada 83
terminology that I think are ill-advised and cause confusion, but this
is the most noticable of them).


Matthew Heaney

unread,
Feb 9, 1997, 3:00:00 AM2/9/97
to

In article <dewar.855461358@merv>, de...@merv.cs.nyu.edu (Robert Dewar) wrote:

>Yes, it is true that Ada 83 was a little confused in this area, but the
>terminology chosen in Ada 95 is fundamentally confusing, and in fact I
>note a couple of error messages in GNAT that are absolutely correct
>but which always cause people trouble:
>
> package x is .... -- oops forgot this hid "type" x
>
> q : x;
> |
> Error: subtype name expected
>
>Almost all programmers react to this message thinking, "why do I need a
>subtype here, a type should be good enough"

Then we have to teach them that "types" don't have a name, only subtypes do.

>
>The fact that
>
> type x is ...
>
>introduces the subtype x is bound to be confusing.

But we had first named subtypes even in Ada 83. This declaration is
(simply) explained as "This declares a type whose first named subtype is
x."

>
>Sometimes we just decide to use the obvious terminology in GNAT instead
>of the technically correct termiology, and we probably should do that
>here too -- the message "type name expected
>would be much clearer in this instance.

Agreed.

>
>For example
>
> subtype x is integer range 1 .. 10;
> for x'size use 5;
>
>GNAT says "rep clause cannot be given for subtype"
>
>
>which is I think clear to people (at least no one ever asked for an
>explanation of this message).

I think it's ambiguous. Do you mean a first named subtype, or non-first
named subtype?

>
>More accurate would be to say
>
> "rep clause may only be given for first named subtype"
>
>but this seems more confusing to us.

Disagree. That is the message I would *expect* to see.


>
>As Bob said, a bit of a mess, and as I say, in my opinion, definitely NOT
>improved in Ada 95 -- I preferred the Ada 83 terminology here (actually
>there are a number of instances where Ada 95 has changed Ada 83
>terminology that I think are ill-advised and cause confusion, but this
>is the most noticable of them).


I'm not disagreeing that this issue can be confusing; after all, I was the
one who submitted the original post! When I was learning Ada 83, I
accepted that "first named subtypes" needed some special syntax to call
that fact out. I think Ada 95 does improve things by always using the term
"subtype" instead of Ada 83's "type or subtype."

After all, we can always refer to the name of the subtype that appears in a
declaration that introduces a type as the "first named subtype," and refer
to other subtypes in that family as "non-first subtype."

But I'll admit it was only after a few years of using Ada 83 that I started
to understand all this business.

It is explained in some books, though, such as Barnes' Programming In Ada
and Bryan & Mendel's excellent Exploring Ada. (You are even quoted in
there, Robert, about the price of houses in Palo Alto.) Sadly, the latter
books (2 vols) are out of print.

Robert Dewar

unread,
Feb 9, 1997, 3:00:00 AM2/9/97
to

Matthew Heany said (replying to me)

<<>which is I think clear to people (at least no one ever asked for an
>explanation of this message).

I think it's ambiguous. Do you mean a first named subtype, or non-first
named subtype?

>
>More accurate would be to say
>
> "rep clause may only be given for first named subtype"
>
>but this seems more confusing to us.

Disagree. That is the message I would *expect* to see.>>

Robert replies:

Right, this is a quite typical reaction. In my experience, the biggest
enemy of good error messages from compilers is people who know too much,
and want the error messages to be technically correct at the level at
which they understand the language.

Matthew you don't *really* think this is ambiguous, in the sense that you
think it would confuse you surely -- since surely you know the language
well enough that you (a) know the rule involved anyway, and (b) you
understand the terminology issue to understand what GNAT means here.
Note that I am not just guessing that this message is clear, my metric
for concern about messages being unclear is a very simple one, I look
to see if anyone ever sends mail saying "what does this mean, I don't
understand".

Error messages should be designed to help people who do NOT know the rule
involved, and threfore, sort of by definition, are NOT experts in the
language. The phrase "first named subtype" is not likely to be in the
vocabulary of beginners, and you often simply confuse people by using
aggressively correct terminology.

Let me give some other examples:

I much prefer the message

"generic package not allowed here"

Rather than

"package name required here"

Eveyrone knows a generic package is not a package, right? wrong!

Another example, I would always use the term package spec in an error
message, rather than package declaration, since in practice the latter,
term, though correct is not familiar.

Going back to types and subtypes, it seems obvious to any beginning
Ada programmer (and to a lot of not so beginning programmers) that

type x is .... introduces a type name

and

subtype x is ... introduces a subtype name

Yes, of course, we experts know that this is wrong, and can have fun
scoffing at the uneducated hoi polloi who don't understand, but error
messages are meant to help people find their errors, NOT send people
somewhere else for help so that they can be educated on proper Ada
terminology.

Of course the best of both worlds would be to have the capability of
further explaining messages in extended text, where it might in some
cases be appropriate to explain the more precise terminology, and
even give an RM reference. As I have mentioned previously, this is a
tool that is on our list of useful things to do.

Meanwhile, my main useful input on error messages continues to be
mail from people who have genuinely found an error message that
confused them in a program they wrote themselves. The error messages
in GNAT have been constantly tuned against such input. I gave an
example in my previous message, a second semester Ada student (so
not someone who was a complete beginner) sent a message to me
with the fragment:

1. procedure q is
2. x : q;
|
>>> subtype mark required in this context

3. begin
4. null;
5. end;

And could not understand the message. That's because the word subtype
set him thinking in the wrong direction. The real error was that he
had accidentally hidden his type name, but the word subtype got him
thinking "why should I need a subtype here, I don't see why a type
is not allowed?"

I also think the word "mark" is gratuitous here, and my inclination is
to change the message to read

>>> type or subtype name required in this context

and in practice I think that would reduce the number of people who got
blocked by this message.

Remember, I never care about the expert who looks at a message, knows
what's wrong immediately, and thinks to themselves "that message is not
quite right". I worry only about avoiding people reading a message and
being puzzled and not being able to figure out how to fix their code
because the message is not sufficiently clear to them.

It's a definite philosophy which is certainly different from say the
inclination to give RM references on all messages. In GNAT we very
occasionally do give RM references if (a) there is a subtle rule that
cannot be summarized in one and (b) the rule in the RM is reasonably
clearly stated and accessible. For example:

1. with System; use System;
2. procedure q is
3. x : address;
4. y : String (1 .. 5) := "hello";
5. for y'Address use x;
|
>>> invalid address clause for initialized object "y"
>>> reference to variable "x" not allowed (RM 13.1(22))

6. begin
7. null;
8. end;

but that is definitely an exception.

Everyone can help improve error messages in GNAT (although I must admit
I will generally pay more attention to beginning students here than to
experts on CLA :-). If you really *do* find an error message that puzzles
you and you can't figure out the error, and think that an error message
change would help, send the comment along to rep...@gnat.com. On the
other hand, if your objection is a tecnical objection from an expert,
and you really knew what was wrong anyway, that's not such critical input.

Robert Dewar
Ada Core Technologies


Matthew Heaney

unread,
Feb 9, 1997, 3:00:00 AM2/9/97
to

In article <dewar.855492829@merv>, de...@merv.cs.nyu.edu (Robert Dewar) wrote:

>Of course the best of both worlds would be to have the capability of
>further explaining messages in extended text, where it might in some
>cases be appropriate to explain the more precise terminology, and
>even give an RM reference. As I have mentioned previously, this is a
>tool that is on our list of useful things to do.

I was wondering about that. I've only been a GNAT user for a week, and I
was a little surprised that the error messages didn't include an RM
reference automatically. I figured there was some switch I could turn on
to get the RM references, too.

Compiling a program tests one's knowledge of the language. From
time-to-time I'll do something I'm not sure is legal (more true now with my
new compiler), to see if it compiles. When I get an error message, I like
to haul out the RM and look it up, to see the rule actually written on the
page.

Admittedly, reading the RM is at times a painful process, but to really
understand the language, you have know how to navigate the RM.

So yeah, I think that adding the switch

-gnatmatt

to display messages for "expert" users would be a swell idea. ;)

Robert Dewar

unread,
Feb 10, 1997, 3:00:00 AM2/10/97
to

Matthew Heany said

<<I was wondering about that. I've only been a GNAT user for a week, and I
was a little surprised that the error messages didn't include an RM
reference automatically. I figured there was some switch I could turn on
to get the RM references, too.

Compiling a program tests one's knowledge of the language. From
time-to-time I'll do something I'm not sure is legal (more true now with my
new compiler), to see if it compiles. When I get an error message, I like
to haul out the RM and look it up, to see the rule actually written on the
page.

Admittedly, reading the RM is at times a painful process, but to really
understand the language, you have know how to navigate the RM.

So yeah, I think that adding the switch

-gnatmatt

to display messages for "expert" users would be a swell idea. ;)
>>


Generally we find RM references pretty useless except to a rather small
group of people who can successfully read the RM. If you can't understand
the message in the first place, most people will not be enlightened by
an RM reference, except in certain unusual cases. It is interesting that
the suggestion to add RM references almost always comes from people who
are pretty much Ada experts, and this is NOT the class of people that
GNAT messages are aimed at!

And no, there is no thought at all of having a switch to display
different messages. The thought is a separate utility program.


Robert Dewar

unread,
Feb 10, 1997, 3:00:00 AM2/10/97
to

Norman suggested:

<<So impressive, in fact, that I'm going to raise the bar: Do you issue
the same warning for unintentionally recursive controlled-type
operations? (For a controlled type CT, these would include a version of
Initialize that creates an uninitialized object of type CT, a version of
Adjust that assigns an object of type CT, or a version of Finalize in
which an object of type CT goes out of existence, either because it was
declared in finalize, because it was uncheckedly deallocated in
Finalize, or because it was created by an allocator for an access type
declared in Finalize.)

Such cases are especially hard to diagnose because the unintentional
recursive call is implicit, i.e., you won't find it in the program text.>>


Robert replies

At least some cases of this kind are caught:

1. with Ada.Finalization; use Ada.Finalization;
2. package a is
3. type m is new controlled with null record;
4. procedure Initialize (Object : in out m);
5. end a;

1. package body a is
2. procedure Initialize (Object : in out m) is
3. x : m;


|
>>> warning: infinite recursion
>>> warning: Storage_Error will be raised at runtime

4. begin
5. null;
6. end;
7. end a;

The diagnostic here is a little mysterious, but I would hesitate to
special case it unless someone really has run into this in practice.
Generally we like to apply the reality check of someone actually
(a) running into an error that could be caught and (b) being
mystified enough to waste time looking for the error. I always
hesitate to add new diagnostics that come only from thought
experiments.

Note that we are happy to get suggestions for error checks and improved
diagnostics. The best form for such suggestions is to send a complete
source program to rep...@gnat.com with a suggestion of exactly what
error message you would like to see. Now sometimes, such suggestions
amount to asking the compiler to read your mind, and it is impossible
to implement them, but it is remarkable how often we *have* been able
to sort things out, even in cases which at first look very hard.

Larry Kilgallen

unread,
Feb 10, 1997, 3:00:00 AM2/10/97
to

In article <mheaney-ya0236800...@news.ni.net>, mhe...@ni.net (Matthew Heaney) writes:
> In article <dewar.855461358@merv>, de...@merv.cs.nyu.edu (Robert Dewar) wrote:
>
>>Yes, it is true that Ada 83 was a little confused in this area, but the
>>terminology chosen in Ada 95 is fundamentally confusing, and in fact I
>>note a couple of error messages in GNAT that are absolutely correct
>>but which always cause people trouble:
>>
>> package x is .... -- oops forgot this hid "type" x
>>
>> q : x;
>> |
>> Error: subtype name expected
>>
>>Almost all programmers react to this message thinking, "why do I need a
>>subtype here, a type should be good enough"
>
> Then we have to teach them that "types" don't have a name, only subtypes do.

And that is a shame, with regard popularization of Ada.

I am not claiming there is any better technical solution, but all who
encounter any situation where Ada is confusing to those experienced
in multiple other programming languages should realize that such a
situation is undesireable.

Larry Kilgallen

Mats Weber

unread,
Feb 10, 1997, 3:00:00 AM2/10/97
to

> Mats, you are confused, the base type (and hence the range of intermediate
> results) is always unconstrained, so that if you write:
>
> subtype Real is digits 7 range 0.0 .. 1.0;

should type instead of subtype, right ?

> as in the original result, you will still get an unconstrained 7 digit
> base type.

So you are saying that

type Real is digits 7 range 0.0 .. 1.0; -- (1)

and

type Real_Base is digits 7;

subtype Real is Real_Base range 0.0 .. 1.0; -- (2)

are equivalent except for the existence of the name Real_Base ?

Well that's not what I get from reading RM 3.5.7(10). In the first case,
I get a safe (and base) range of at least -1.0 .. 1.0, and in the
second,
- 10.0 ** 28 .. 10.0 ** 28.

Now I know that this is not true for any reasonable implementation
because floating point base types are always created from a finite set
of representations available on the machine, but according to the RM
there could exist a legal implementation of Ada 95 for which 100.0 is a
possible value of Real'Base in variant (2) but not in variant (1). Or am
I missing soemthing ? (If I am, then the RM is confusing).

Robert Dewar

unread,
Feb 11, 1997, 3:00:00 AM2/11/97
to

Robert said

<<type Real is digits 7 range 0.0 .. 1.0;

as in the original result, you will still get an unconstrained 7 digit
base type.>>

Mats said

<<Now I know that this is not true for any reasonable implementation
because floating point base types are always created from a finite set
of representations available on the machine, but according to the RM
there could exist a legal implementation of Ada 95 for which 100.0 is a
possible value of Real'Base in variant (2) but not in variant (1). Or am
I missing soemthing ? (If I am, then the RM is confusing).>>

Robert says

I was definitely only talking about imaginable implementations. You cannot
imagine an implementation that has a 7 digit floating machine type which
excludes the value 100.0.

That's in the same realm as noting that the above declaration may raise
Storage_Error!

And I see nothing in the RM that would stop you choosing this base type
in both cases if your machine actually had it!


Robert I. Eachus

unread,
Feb 11, 1997, 3:00:00 AM2/11/97
to

In article <dewar.855492829@merv> de...@merv.cs.nyu.edu (Robert Dewar) writes:

> 5. for y'Address use x;
> |
> >>> invalid address clause for initialized object "y"
> >>> reference to variable "x" not allowed (RM 13.1(22))

Boy, I don't like that one, but for a different reason. I think
that any diagnostic for an implementation dependent rule should say
so. i.e. "reference to variable "x" not allowed by GNAT (see RM
13.1(22))" would be much more helpful, even to non-novice users.

When porting code from one system to another these are the messages
that show up. Somethimes the workaround is easy sometimes hard, but
it is a royal PAIN to spend time searching for why the blasted thing
doesn't work. A diagonstic like this will have users looking at what
has been changed in the declaration of x, rather than starting to work
on the real problem.

Robert Dewar

unread,
Feb 12, 1997, 3:00:00 AM2/12/97
to

Robert Eachus said

<< When porting code from one system to another these are the messages
that show up. Somethimes the workaround is easy sometimes hard, but
it is a royal PAIN to spend time searching for why the blasted thing
doesn't work. A diagonstic like this will have users looking at what
has been changed in the declaration of x, rather than starting to work
on the real problem.>>

I'll say it again :-)

I never pay too much attention to what the Ada experts think *might* be
confusing. I pay attention to what real users actually *do* find confusing.
I find this a very useful distinction!

In this particular case, the RM reference is really very clear, and since
we changed this particular message, we (so far) have not had anyone be
mystefied by this particular message.

0 new messages