How can I overload the '=' operator in Ada without creating a recursive function?

Tag: overloading , ada Author: lovelives Date: 2011-05-06
FUNCTION "=" (lString1, lString2 : IN lString) RETURN boolean IS


     IF lString1 = NULL AND lString2 = NULL THEN 
        RETURN true;
      ELSIF lString1 = NULL OR lString2 = NULL THEN
        RETURN false;
      END IF;

I'm trying to overload the equality operator in Ada. Each time I use the operator '=' within the function it causes a recursion which leads to a stack overflow, rather than use the ada defined operator which I need. Is there a way to differentiate it from my overloaded operator?

Is this question of any assitance?
Hmm, I thought you could use Standard."="(lString1, NULL), but GNAT balks at that, telling me I've got "incompatible arguments". Even when I replaced null with a constant of the lString type--set to Null--I still got the same error. Curious...
@ Will: Unfortunately I don't do a very good job of reading that code, because Ada is my first programming language.
@ Marc: I figured there was a way to call the standard operator by specifying the package, but I haven't figured it out yet.
You shouldn't overload "=" for access types.

Other Answer1

By introducing a non-overloaded utility function to do the access type comparisons, the OP's function definition, with the needed syntax fixes and modified to use the utility function, can be made to work.

I'm still puzzled, though, as to why invoking "=" as Standard."=" is rejected by the compiler (GNAT) for specifying "incompatible arguments".

with Text_IO; use Text_IO;

procedure non_recursive_equals is

   type Lstring is access String;

   -- Be aware, the ordering of the functions here is important!
   function Is_Equal(Lstring1, Lstring2 : in Lstring) return Boolean is
   begin
      return Lstring1 = Lstring2;
   end Is_Equal;

   function "=" (lString1, lString2 : in Lstring) return Boolean is
   begin
      if Is_Equal(LString1, null) and Is_Equal(LString2, null) then
         return True;
      elsif Is_Equal(LString1, null) or Is_Equal(LString2, null) then
         return False;
      end if;
      return False;
   end "=";

   L1, L2 : Lstring := null;

begin
   Put_Line("L1 and L2 null: " & Boolean'Image(L1 = L2));
   L2 := new String(1..10);
   Put_Line("L2 not null   : " & Boolean'Image(L1 = L2));
end non_recursive_equals;

Edit:

Here's another way, using a renames clause:

with Text_IO; use Text_IO;

procedure non_recursive_equals is

   type Lstring is access String;

   function Is_Equal (lString1, lString2 : in Lstring) return Boolean is
   begin
      if lString1 = null and lString2 = null then
         return True;
      elsif lString1 = null or lString2 = null then
         return False;
      end if;
      return False;
   end Is_Equal;

   function "=" (Lstring1, Lstring2 : in Lstring) return Boolean renames
     Is_Equal;

   L1, L2 : Lstring := null;

begin
   Put_Line ("L1 and L2 null: " & Boolean'Image (L1 = L2));
   L2 := new String (1 .. 10);
   Put_Line ("L2 not null   : " & Boolean'Image (L1 = L2));
end non_recursive_equals;

comments:

Nice work around the problem, and useful if you can limit the scope of your use of the new = function. I think trying to make it visible in a broader package body or via a package spec will give us the same problem though.
@Greg: Very likely. Someone oughtta hit comp.lang.ada with this question, since there's some real language lawyers over there (I can't get to it from where I'm at right now). I'm especially curious about the reason for the "inappropriate argument" error I got when trying to use Standard."=" to do the comparison inside the function. (To see for oneself, within function "=" replace each reference to Is_Equal with Standard."=")
Marc C: Using gcc 4.3.4, Standard."="(Lstring1.all, "") compiles, but fails at runtime with access check failed. +1 example, btw.

Other Answer2

I was able to reproduce the same behavior with similar code. I took the liberty of assuming that lString was some sort of string access type

I believe the recursion is being caused by the fact that your new = function masks the natively provided one. Since they share both the same name, parameters, and return value, there is no straightforward way of distinguishing between the two.

An inelegant way around this would be to avoid overloading entirely and to define a new function with the same behavior as your overloaded function, with a different name such as Is_Equal.

Other Answer3

Here's yet another way, using only Ada83... and a horrid example/abuse of exceptions:

  Type LString is Access String;

  Function "=" (Left, Right: IN LString) Return Boolean is
     Subtype Constrained_LString is Not Null LString;

     Function Is_Equal( Left : LString; Right : String ) Return Boolean is
     begin
        Return Right = Left.All;
     exception
        When CONSTRAINT_ERROR => Return False;
     end Is_Equal;

  Begin
     Return Is_Equal(Left, Right.All);
  Exception
     When CONSTRAINT_ERROR =>
        begin
           Return Is_Equal(Right,Left.All);
        Exception
           When CONSTRAINT_ERROR => Return True;
        end;
  End "=";

What happens is if it is called and Right = Null the attempt to de-reference it causes an exception; in this case we try to de-reference Left and if that too fails then both must be Null. In the case where only one fails the equality must be false and in the case where both parameters can be de-referenced the result is the test for equality on those strings.

Other Answer4

I'm not sure why "=" is being used recursively; possibly, there is an unfortunate use clause present. The example below overloads "=" and produces the following output. The overloaded function implicitly invokes Standard."=" for the comparison. Note you can specify renames to simplify package names, and you can use type to expose just the operators applicable to a type.

Addendum: I've added an alternate way to invoke Standard."=" in a comment below.

Console:

********************
********************
TRUE
TRUE

Code:

with Ada.Strings.Bounded;
with Ada.Strings.Unbounded;
with Ada.Text_IO;

procedure UseType is
   package String20 is new Ada.Strings.Bounded.Generic_Bounded_Length(20);
   use type String20.Bounded_String;

   package StringN renames Ada.Strings.Unbounded;
   use type StringN.Unbounded_String;

   function "=" (Left  : String20.Bounded_String;
                 Right : StringN.Unbounded_String) return Boolean is
   begin
      return String20.To_String(Left) = StringN.To_String(Right);
      -- return Standard."="(String20.To_String(Left), StringN.To_String(Right));
   end "=";

   SB : constant String20.Bounded_String := 20 * '*';
   SN : constant StringN.Unbounded_String := 20 * '*';

begin
   Ada.Text_IO.Put_Line(String20.To_String(SB));
   Ada.Text_IO.Put_Line(StringN.To_String(SN));
   Ada.Text_IO.Put_Line(Boolean'Image(SB = SN)); -- infix operator
   Ada.Text_IO.Put_Line(Boolean'Image("="(SB, SN))); -- named operator
end UseType;

comments:

+1 for the use type reference