How come that Lady Ada spoiled the previously so delicious meal with the two marked declarations. (In presence of the beverages, the compiler rejects the programm because of the ambiguity of the catenation operators "&", in absense it is legal).
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; -- * Beverages: array (1 .. Courses) of Wine; -- * procedure Enjoy (Choice: in Meal) is separate; begin Enjoy (Meal (Menu & (Menu'Length + 1 .. Courses => ' '))); end Table;
Before attacking the problem, we first want to keep a close eye on our lady. The menu contains a certain number of dishes. We can choose from it a meal with a maximum of 10 courses. When we choose exactly 10, the aggregate
(Menu'Length + 1 .. Courses => ' ')
is empty; else it fills the sequence of dishes to exactly 10 courses by adding the necessary amount of empty courses. And for what serves the type conversion in the call of Enjoy? Well, we must not assume that the menu starts with entry number 1 - it's therefore that we use the attribute 'Length instead of 'Last. Thus the expression
Menu & (Menu'Length + 1 .. Courses => ' ')
gets the limits Menu'First and Menu'First + 9, RM_83 4.5.3 (4). Requested however is the range 1..10, and that's exactly what the subtype conversion Meal(X) is doing under the precondition that X has the correct length: The expression X keeps its value, only its limits slide.
Now I'm seeing Lady Ada sitting in her new house, laughing up her sleeve: In Ada 95, this would not have happened, because in such a case the subtype conversion (sliding) is done automatically, RM_95 4.6 (60), so that we can write directly
Enjoy (Menu & (Menu'Length + 1 .. Courses => ' '));
without raising Constraint_Error, and - magically - the ambiguity is gone.
Why? We are dealing here with a quite general problem, which appears in many disguises. Normally it appears when different string types are used:
type My_String is new String; My_String ('X' & 'Y')
but the cause is not the component type (here Character). Anybody bumping into a problem like this is driven to dispair, especially so because compiler messages normally are not very helpful and the declaration to blame (of the kind of Beverages) may be well hidden in some other package appearing in a use-clause. Here I would have bet my head that I was dealing with a compiler bug. What only made me suspicious, however, was three compilers being fed this program all responding with some more or less intelligible message complaining about the ambiguity of the catenation operator "&".
A funny abridged version of the problem goes like this:
Are the expressions "Lady" & " Ada" and "Lady Ada" the same?
In the following I quote Lars Prins's solution of Rational, adapted to our Table.
It seems that the declaration of Beverages is irrelevant here, but in fact it is not! According to RM_83 3.6 (10), the declaration of Beverages is equivalent to
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; subtype Wine_Index is Positive range 1 .. 10; type Wine_Cellar is array (Wine_Index range <>) of Wine; subtype Wine_List is Wine_Cellar (Wine_Index); procedure Enjoy (Choice: in Meal) is separate; begin Enjoy (Meal (Menu & (Menu'Length + 1 .. Courses => ' '))); | end Table; ambiguous
The procedure can be simplified a bit without losing the ambiguity:
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; subtype Wine_Index is Positive range 1 .. 10; type Wine_Cellar is array (Wine_Index range <>) of Wine; subtype Wine_List is Wine_Cellar (Wine_Index); Inedible: Meal := Meal ("Fried eggs " & "and spinach"); ^ ambiguous begin null; end Table;
According to RM 4.6 (3), the type of the operand of a type conversion must be determinable independently of the context (in particular, independently of the target type). This means that for the resolution of the "&" operator, the fact that the result will be converted to the type Meal may not be used. Using a different type for the conversion, for instance, results in the same error. The fact that a type conversion to Meal was used is irrelevant indeed.
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; subtype Wine_Index is Positive range 1 .. 10; type Wine_Cellar is array (Wine_Index range <>) of Wine; subtype Wine_List is Wine_Cellar (Wine_Index); Inedible: Boolean := Boolean ("Fried eggs " & "and spinach"); ^ ambiguous begin null; end Table;
To show in which ways the expression can be interpreted, two unambiguous declarations are added. The ambiguity is resolved in these cases by taking away the type conversion, which puts an extra requirement on the resolution of the "&" operator:
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; subtype Wine_Index is Positive range 1 .. 10; type Wine_Cellar is array (Wine_Index range <>) of Wine; subtype Wine_List is Wine_Cellar (Wine_Index); Inedible: Boolean := Boolean ("Fried eggs " & "and spinach"); ^ ambiguous -- No longer ambiguous are the following usages of the -- "&" operator: Drinkable: String (1 .. 10) := ("Mosel" & " Wine"); Also_Drinkable: String (1 .. 10) := ("Mosel Wine"); Acceptable: Wine_Cellar (1 .. 2) := ("Mosel" & "Rhine"); Also_Acceptable: Wine_Cellar (1 .. 2) := (1 => "Mosel", 2 => "Rhine"); -- Again ambiguous: Disgusting: Wine_Cellar (1 .. 2) := ((1 => "Diethylene") & (2 => "glycole")); -- In this case, the ambiguity is introduced by the fact that the -- compiler is not allowed to look inside the aggregates to -- determine which "&" operator is meant (RM 4.3 (8)). begin null; end Table;
Quote end.
Everything understood? OK, this takes some time to digests. Basically it is about the compiler's inability to decide whether the concatenation of the two operands shall result in a Meal or in a Wine_Cellar. (In both cases, the conversion to Boolean is of course absolute nonsense - but that does not matter, as little as the wrong lengths of the strings!)
The problem may be cured in Ada 83 for example in the following way:
procedure Table (Menu: in String) is Courses: constant := 10; subtype Meal is String (1 .. Courses); subtype Wine is Meal; subtype Wine_Index is Positive range 1 .. 10; type Wine_Cellar is array (Wine_Index range <>) of Wine; subtype Wine_List is Wine_Cellar (Wine_Index); procedure Enjoy (Choice: in Meal) is separate; function Adulterate (X: String) return Wine is begin return Meal (X); end Adulterate; begin Enjoy -- (Meal (Menu & (Menu'Length + 1 .. Courses => ' '))); (Adulterate (Menu & (Menu'Length + 1 .. Courses => ' '))); end Table;
With the function Adulterate, this nearly looks the same as before, but there is a fundamental syntactic and semantic difference: A type conversion only looks like a function call, but is none, no Ada function specification can be given for it (whence it does not appear as a function call in the syntax)! The specification ought to have the form
function Type_Mark (X: Any_other_Type_Mark) return Type_Mark;
Now the requirement becomes obvious why a compiler must establish the operand type without consideration of the context (RM 4.6 (3)).
Something similar is true for some attributes. Given two overloaded functions
function F (X: Some_Type) return Long_Integer; function F (X: Some_Type) return Integer;
we again get ambiguity:
I: Whole_Number := Whole_Number (F (X)); ^ ambigous J: Whole_Number := Whole_Number'Val (F (X)); ^ ambiguous
Neither for the 'Val attribute a specification can be given.
Still another solution is possible:
Enjoy (Meal (String'(Menu & (Menu'Length + 1 .. Courses => ' '))));
And with this, we have finally arrived at the Ada solution proper for such cases.
Contents | © Copyright 1998 C.K.W. Grein |