From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,d57302f2954365e1 X-Google-Attributes: gid103376,public From: bobduff@world.std.com (Robert A Duff) Subject: Re: Question about base types Date: 1997/01/27 Message-ID: X-Deja-AN: 212715099 references: organization: The World Public Access UNIX, Brookline, MA newsgroups: comp.lang.ada Date: 1997-01-27T00:00:00+00:00 List-Id: In article , Matthew Heaney 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 is System.Min_Int .. System.Max_Int; >subtype T is 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 is range System.Min_Int .. System.Max_Int; >subtype T is 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 : ; 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