!standard 4.3.1(17) 01-11-02 AC95-00017/01 !class uninteresting 01-11-02 !status received 01-11-02 !subject Are discriminant constraints required to be static? !summary !appendix From: Dan Eilers Sent: Friday, November 2, 2001 4:23 PM !topic are discriminant constraints required to be static !reference RM95-3.7.1 !from Dan Eilers 01-11-02 !discussion procedure test1 is type enum is (e1,e2,e3,e4,e5); subtype subset is enum range e1..e4; type rec(x: enum) is record case x is when subset => i,j,k: integer; when others => null; end case; end record; type rec_subset(y: subset := e1) is new rec(x => y); function create(z: subset) return rec_subset is r: rec_subset := (z, 0, 0, 0); -- legal?? begin return r; end; a: rec := rec(create(e2)); begin null; end test1; Does the discriminant constraint z (above) have to be static, or is it sufficient that z's subtype statically matches (in the sense of 4.9.1) the discriminant subtype for y on type rec_subset? If it must be static, is there a better way to define create than the following? function create(z: subset) return rec_subset is r: rec_subset; begin case z is when e1 => r := (e1, 0, 0, 0); when e2 => r := (e2, 0, 0, 0); when e3 => r := (e3, 0, 0, 0); when e4 => r := (e4, 0, 0, 0); end case; return r; end; What I'd really like to do is use a subtype instead of the derived type rec_constraint (below), eliminating the need for a type conversion in the declaration of a. Why isn't this allowed? procedure test2 is type enum is (e1,e2,e3,e4,e5); subtype subset is enum range e1..e4; type rec(x: enum) is record case x is when subset => i,j,k: integer; when others => null; end case; end record; subtype rec_subset(y: subset := e1) is rec(x => y); -- why not allow? function create_subset(z: subset) return rec_subset is r: rec_subset := (z, 0, 0, 0); begin return r; end; a: rec := create_subset(e2); -- no type conversion desired begin null; end test2; **************************************************************** From: Jeffery Carter Sent: Friday, November 2, 2001 5:01 PM The discriminant in the aggregate must be static, but you can always do: R : Rec (X => Z); ... R.I :=0; R.J := 0; R.K := 0; return R; **************************************************************** From: Tucker Taft Sent: Friday, November 2, 2001 5:41 PM 4.3.1(17) is quite clear that in an aggregate, the discriminant that governs a variant part must be given by a static expression. You are right that the rule could have allowed a non-static value so long as its value was known statically to be within a range that determined which variant alternative was chosen. Of course such a rule would be harder to state and harder to implement, so I presume the perceived value associated with the more liberal rule was not sufficient to offset this added complexity. [A related annoyance is that you must have an others alternative in a nested variant part even if all possible choices are already covered, since the relevant range is the full range of the discriminant, rather than the subset of the range associated with the enclosing variant alternative(s).] **************************************************************** From: Christoph Grein Sent: Monday, November 5, 2001 12:48 AM By changing your example, you can accoplish this: > procedure test1 is > type enum is (e1,e2,e3,e4,e5); > subtype subset is enum range e1..e4; > type rec (x: enum) is record > case x is > when subset => i,j,k: integer := 0; <----- defaults > when others => null; > end case; > end record; > type rec_subset(y: subset := e1) is new rec(x => y); > function create(z: subset) return rec_subset is > r: rec_subset (z); <----- need not be static > begin > return r; > end; > a: rec := rec(create(e2)); > begin > null; > end test1; It's sometimes annoying that discriminants in aggregates have to be static, but I think this is for good reasons. With the above trick, you can circumvent the case statement. Note that the variable r is never assigned a value, yet it is fully defined. This conflicts with some coding standards which require that all variables must be initialized or assigned a value. **************************************************************** From: Dan Eilers Sent: Wednesday, November 7, 2001 1:10 PM Thanks! That trick solved the problem I was having. But I disagree that there are good reasons for restricting the discriminant to be static. Tuck wrote: > 4.3.1(17) is quite clear that in an aggregate, the discriminant > that governs a variant part must be given by a static > expression. You are right that the rule could have allowed > a non-static value so long as its value was known statically > to be within a range that determined which variant alternative > was chosen. Of course such a rule would be harder to state > and harder to implement, so I presume the perceived value > associated with the more liberal rule was not sufficient to > offset this added complexity. Is it really that hard to state and/or implement? It doesn't appear that this issue was thought through very well. The AARM, for example, makes no mention that a more liberal rule would have worked. In accordance with the principle of least surprise, and to avoid the need to teach such non-obvious tricks, I propose liberalizing the rule to allow static scalar subtypes instead of just static values. 4.3.1(17) would be changed from: "If the components of a variant_part are needed, then the value of a discriminant that governs the variant_part shall be given by a static expression." to something like: "If the components of a variant_part are needed, then either o the value of a discriminant that governs the variant_part shall be given by a static expression; or o the discriminant shall be of a static constrained scalar subtype, whose values are all covered by the same variant. with a cross reference to the definition of "covered" in 3.8.1(9). The logic to determine whether a static scalar subtype overlaps more than one variant is already in compilers in order to enforce the rule that variant parts don't overlap each other, so there seems to be very little implementation burden. p.s. I didn't get any comments on the second part of my question. Is there a good reason why subtype declarations aren't like type declarations in allowing a discriminant part? > What I'd really like to do is use a subtype instead of the derived > type rec_constraint (below), eliminating the need for a type conversion > in the declaration of a. Why isn't this allowed? > > procedure test2 is > type enum is (e1,e2,e3,e4,e5); > subtype subset is enum range e1..e4; > type rec(x: enum) is record > case x is > when subset => i,j,k: integer; > when others => null; > end case; > end record; > subtype rec_subset(y: subset := e1) is rec(x => y); -- why not allow? > function create_subset(z: subset) return rec_subset is > r: rec_subset := (z, 0, 0, 0); > begin > return r; > end; > a: rec := create_subset(e2); -- no type conversion desired > begin > null; > end test2; **************************************************************** From: Robert Dewar Sent: Wednesday, November 7, 2001 1:25 PM << I didn't get any comments on the second part of my question. Is there a good reason why subtype declarations aren't like type declarations in allowing a discriminant part?>> This question is asked the wrong way round, there has to be a good reason for adding a feature, not lack of a good reason for keeping it out! To me this would be added complexity that is definitely not worth while. **************************************************************** From: Dan Eilers Sent: Wednesday, November 7, 2001 1:48 PM Well, I did provide an example where a subtype was desired, and having to use a type as a workaround results in cluttering the code with unwanted type conversions. RM95 Introduction, para 39, gives the purpose of subtypes: 39 The concept of a type is further refined by the concept of a subtype, whereby a user can constrain the set of allowed values of a type. Subtypes can be used to define subranges of scalar types, arrays with a limited set of index values, and records and private types with particular discriminant values. I want to do just that, use a subtype to constrain the discriminants of a record type, but instead of constraining them to a single value, I want to contrain them to a range of values, just as a type declaration can do. Orthogonality of language design would argue for subtype declarations to behave like type declarations in their ability to constrain discriminants to a range of values, unless there is some reason why it causes difficulties for implementations and/or users. **************************************************************** From: Robert Dewar Sent: Friday, November 2, 2001 5:41 PM I understand the request, but I think it introduces excessive complexity, with insufficient justification of real need. **************************************************************** From: Christoph Grein Sent: Wednesday, November 7, 2001 11:38 AM > > type enum is (e1,e2,e3,e4,e5); > > subtype subset is enum range e1..e4; > > type rec(x: enum) is record > > case x is > > when subset => i,j,k: integer; > > when others => null; > > end case; > > end record; > > subtype rec_subset(y: subset := e1) is rec(x => y); -- why not allow? A possible complication are named aggregate associations. What is the name of the component? Ag_S: rec_subset := (y => e1, i => ...); Ag_R: rec (e1) := (x => e1, i => ...); This is a bit confusing, although with the context of the subtype given, might seem OK. But what about aggregate parameters of subprograms: procedure P (X: Rec); P ((y => e1, i => ...)); -- subtype aggregate, legal? P ((x => e1, i => ...)); -- OK It should be possible to supply aggregates of subtypes for parameters of the "full" type. Here, usage of the component name y is really confusing. **************************************************************** From: Dan Eilers Sent: Thursday, November 8, 2001 1:26 AM Christoph Grein wrote: > A possible complication are named aggregate associations. > > What is the name of the component? > > Ag_S: rec_subset := (y => e1, i => ...); > Ag_R: rec (e1) := (x => e1, i => ...); > > This is a bit confusing, although with the context of the subtype given, > might seem OK. I think the context makes these cases pretty clear. The aggregates on the right-hand-side should be interpreted based on the subtype specified on the left-hand-side. > But what about aggregate parameters of subprograms: > > procedure P (X: Rec); > > P ((y => e1, i => ...)); -- subtype aggregate, legal? > P ((x => e1, i => ...)); -- OK > > It should be possible to supply aggregates of subtypes for parameters of the > "full" type. Here, usage of the component name y is really confusing. The first call should not be legal, and hence no confusion, since the parameter is declared to be of the subtype Rec, and there is no rec_subset anywhere in sight to cause confusion. Of course, if the aggregate was qualified to be of subtype rec_subset that would make it legal, but would also eliminate any potential confusion. **************************************************************** From: Robert Duff Sent: Tuesday, November 13, 2001 11:02 AM > I didn't get any comments on the second part of my question. > Is there a good reason why subtype declarations aren't like > type declarations in allowing a discriminant part? It seems very strange to me to have different subtypes of the same type use different names for the same component. I agree that it would be desirable to somehow be able constrain discriminants to be in a range (or even an arbitrary list of values!), instead of just a single value. But it doesn't seem worth changing at this point. **************************************************************** ****************************************************************