!standard 3.9.3(4/3) 10-02-09 AI05-0198-1/01 !class binding interpretation 10-02-09 !status work item 10-02-09 !status received 09-07-01 !subject Inheriting abstract operators for untagged types !summary If the corresponding operator of the parent type is abstract, a predefined operator for an untagged derived type is abstract. !question Consider: package Pack is type Unit is new Float; function "*" (L, R : Unit) return Unit is abstract; type Unit_Squared is new Unit; -- function "*" (L, R : Unit_Squared) return Unit_Squared is abstract; -- function "*" (L, R : Unit_Squared) return Unit_Squared is -- ; end Pack; When the derived type Unit_Squared is declared, the implicit declaration of two homographs occurs. The first one is inherited from the parent type, and like the parent subprogram is abstract (3.9.3(5)). The second one is defined because Unit_Squared is a floating-point type and 4.5.5(11) says that this operation is defined for every floating-point type. These two implicitly declared functions are homographs, and they're implicitly declared at the same place. Now, 8.3(11) says "the implicit declaration of an inherited operator overrides that of a predefined operator" [when they're homographs]. So the first implicit function overrides the second. 8.3(12/2) says that when two homographs are implicitly declared at the same place, then if at least one is a subprogram that is neither null nor abstract, and does not require overriding, then they override all those that are null or abstract. So the second implicit function overrides the first. Something is wrong here. !recommendation (See Summary.) !wording Modify 3.9.3(4/3) (as previously modified by AI05-0097-1): If a type has an implicitly declared primitive subprogram that is inherited or is {a}[the] predefined [equality] operator, and the corresponding primitive subprogram of the parent or ancestor type is abstract or is a function with a controlling access result, or if a type other than a non-abstract null extension inherits a primitive function with a controlling result, then: !discussion There are four possibilities as to what happens here: (1) The predefined operation overrides the abstract one; (2) The abstract operation overrides the predefined one; (3) Both override each other; (4) Neither override the other. (1) would mean that the predefined operation would "reemerge" for a type where the operation was "hidden". (4) would have the same meaning, as the abstract operation is ignored for the purposes of resolution. That would most likely be exactly what the programmer *doesn't* want, so we reject (1) and (4). Both (2) and (3) have the behavior we want, which is for the operator to be not considered for resolution purposes. (2) has that effect because the declared operator is abstract, 6.4(8) says it is not considered for resolution. (3) has that effect as both operators are hidden from all visibility because they are overridden. Since (3) is the current state, we wouldn't have to make a language change. But the semantics of two operators overriding each other is just too weird; it looks like a mistake. And an easy solution is available. This problem does not occur for predefined "=", because 3.9.3(4-5) makes it abstract. Thus both operators are abstract and there is not a problem. (Well, there is a still a tiny glitch in that 8.3(12.3/2) says that one of the homographs is chosen arbitrarily; 8.3(11) choses a particular one. Those aren't necessarily inconsistent, however, so this isn't worth fixing.) "=" was made abstract to avoid problems with tagged "=", but it appears to have fixed another problem as well. And it only makes sense that an operator is abstract if the corresponding operator of the parent is abstract. So we just change 3.9.3(4) to apply to all predefined operators. This change is compatible (presuming that the implementation handles this case as (2) or (3) as intended, and not as (1) or (4)), since the only abstract operators that require overriding are those for tagged types. And the only predefined operator for a tagged type is "=", which was already covered by this wording. --!corrigendum 3.9.3(4/2) !ACATS Test An ACATS B-Test should be created that is similar to the example in the question. !appendix !topic Possible ambiguity with overriding rules !reference 8.3(11,12.2) !from Adam Beneschan 09-07-01 !discussion Yet another possible candidate for "least important AI ever": package Pack is type Unit is new Float; function "*" (L, R : Unit) return Unit is abstract; type Unit_Squared is new Unit; -- function "*" (L, R : Unit_Squared) return Unit_Squared is abstract; -- function "*" (L, R : Unit_Squared) return Unit_Squared is -- ; end Pack; From what I can tell, when the derived type Unit_Squared is declared, this causes the implicit declaration of two homographs. The first one is inherited from the parent type, and like the parent subprogram is abstract (3.9.3(5)). The second one is defined because Unit_Squared is a floating-point type and 4.5.5(11) says that this operation is defined for every floating-point type. These two implicitly declared functions are homographs, and they're implicitly declared at the same place. Now, 8.3(11) says "the implicit declaration of an inherited operator overrides that of a predefined operator" [when they're homographs]. So the first implicit function overrides the second. 8.3(12/2) says that when two homographs are implicitly declared at the same place, then if at least one is a subprogram that is neither null nor abstract, and does not require overriding, then they override all those that are null or abstract. So the second implicit function overrides the first. Is this really what happens---that both implicit functions override each other? Or does one of them win, and if so, which one? If it's supposed to be the latter, do the rules in 8.3 need to be tweaked to make one of the rules not apply? **************************************************************** From: Edmond Schonberg Sent: Wednesday, July 1, 2009 4:20 PM This is addressed by AI95-0310 : the abstract non-dispatching subprogram does not participate in overloading resolution. This is stated in 6.4 (8) : the name or prefix shall not resolve to denote an abstract subprogram unless it is also a dispatching subprogram. Unfortunately this reference to Abstract Subprogram does not appear in the index. **************************************************************** From: Adam Beneschan Sent: Wednesday, July 1, 2009 4:37 PM > This is addressed by AI95-0310 : the abstract non-dispatching > subprogram does not participate in overloading resolution. No, that doesn't address the problem, which has to do with visibility, not overload resolution. In the case I'm describing, there are two implicitly declared homographs. They cannot both be visible since they are homographs. That leaves three possibilities: (1) The predefined operation overrides the abstract one; (2) The abstract operation overrides the predefined one; (3) Both override each other, which might mean that neither one is visible, or it might be just plain nonsensical. I'm not sure the rules are clear on which one is correct. AI95-310, by contrast, deals with a case where subprograms are *not* homographs: function "*" (L, R : Unit) return Unit is abstract; function "*" (L, R : Unit) return Unit_Squared; Since they are not homographs, both are visible. AI95-310 is just there to make sure this fact doesn't cause any construct to be considered ambiguous. But it's a totally different problem. (Yeah, I extracted the example from AI95-310, so I can understand why it might have confused you. I'll try not to do that next time.) **************************************************************** From: Edmond Schonberg Sent: Wednesday, July 1, 2009 5:09 PM You are correct, AI95-0310 is not directly relevant, but at least it suggests that the desired reading is in fact (2) in your list below, because the purpose of defining the abstract operation is to "undefine" the primitive one. I would thing that this "undefinition" is preserved in the derivation. Certainly the call USQ * USQ should be illegal, so we definitely want to exclude (1). In any case, the rules could use some clarification. **************************************************************** From: Randy Brukardt Sent: Wednesday, July 1, 2009 5:11 PM > No, that doesn't address the problem, which has to do with visibility, > not overload resolution. Right. For the record, I agree with Adam that there is a problem. > In the case I'm describing, there are two implicitly declared > homographs. They cannot both be visible since they are homographs. Strictly speaking, that's not true; there are ways for homographs to be both visible (from an instance, for example). But in any such case, any call is ambiguous. Note that the requirement for no homographs only applies to "non-overriddable" declarations. Not particularly relevant to the point. > That leaves three possibilities: > > (1) The predefined operation overrides the abstract one; > (2) The abstract operation overrides the predefined one; > (3) Both override each other, which might mean that neither one is > visible, or it might be just plain nonsensical. Well, there are four, really: (4) Neither override the other and all calls are ambiguous. > I'm not sure the rules are clear on which one is correct. Not clear seems to be an understatement. The rules seem to say that both (1) and (2) are true, which is impossible. Now, I know there is a principle that inherited operations always override predefined operations, so I'm pretty sure that the intent is (2). But the wording doesn't say that. It is interesting that this problem doesn't happen with "=". (You should have mentioned that, because it seems highly significant; surely if it happened with "=", it would be much more likely to happen, and this would actually be important.) The reason for that is an interaction with a rule adopted solely for tagged types, the special case for predefined equality in 3.9.3(4-6). Recall that "abstract" isn't actually inherited, but rather it is recalculated each time a subprogram is inherited. package Pack1 is type Unit is private; function "=" (L, R : Unit) return Boolean is abstract; private ... end Pack1; with Pack1; package Pack2 is type New_Unit is new Pack1.Unit; -- function "=" (L, R : New_Unit) return Boolean; -- Predefined (11) -- function "=" (L, R : New_Unit) return Boolean; -- Inherited (12) end Pack; 3.9.3(4-6) says that (12) is abstract (since New_Unit is untagged, 3.9.3(5) applies). It also says that (11) is abstract, since this is a predefined equality operator, and thus 3.9.3(5) applies to it as well. That means the 8.3(12.3/2) applies (all are abstract and all are fully conformant), we don't care which one is visible, but one is. (This rule was adopted for tagged types because predefined equality is not inherited for those; it is reconstructed, but we surely want the child type's equality to be abstract if the parent type's is abstract.) The obvious fix here is to make this rule apply to all predefined operators (not just equality). If a predefined operator is overridden as abstract, it seems weird for a derived type to allow the predefined operator to reemerge and not continue to be abstract. Probably that wasn't supposed to happen because it was supposed to always be overridden. The other fix would be to give a preference to the bullets of 8.3(9-13), so that once 8.3(11) is applied, we don't look further. But that might cause problems when there are three homographs involved (easy to construct with multiple derivations). I think it is easier to say that the predefined operators are abstract - but perhaps there is some implication of that that I am forgetting. **************************************************************** From: Jean-Pierre Rosen Sent: Thursday, July 2, 2009 1:17 AM > Well, there are four, really: > (4) Neither override the other and all calls are ambiguous. Hmmm... Since one of them is abstract, AI95-0310 would apply (this time), so the call would not be ambiguous. **************************************************************** From: Edmond Schonberg Sent: Thursday, July 2, 2009 7:58 AM That can't possibly be the outcome, it would be a re-emergence of an operation that was "undefined" in the parent. So the intent (which we are trying to intuit here) is that the user-defined (abstract) operation overrides the predefined one, and the call is to the abstract operation, which must be rejected by the compiler to indicate that an explicit overriding is needed. ****************************************************************