[Solved] Access to a "method"?

Hi,

I’m writing a small library. I’m now sealing few records that were in the API into tagged private records with getters and setters.

However members of the records were ‘in out’ parameters of some procedures. I’ve tried a few things but I can’t find a proper way/syntax to replace Process (Item.Member) with an equivalent Process (Item.Set_Member).

In the example (simplified) code below, I get error: no selector "Set_Member" for private type "Test" if I uncomment the last line.

with Ada.Wide_Wide_Text_IO;

procedure Bacasable is

   package AIO renames Ada.Wide_Wide_Text_IO;

   package Test_P is

      --  Old record
      --  type Test is record
      --     Member : Integer := 0;
      --  end Test;

      --  New sealed record
      type Test is tagged private;
      procedure Set_Member (T : in out Test; Value : Integer);

   private

      type Test is tagged record
         Member : Integer := 0;
      end record;

   end Test_P;

   package body Test_P is

      procedure Set_Member (T : in out Test; Value : Integer) is
      begin
         T.Member := Value;
      end Set_Member;

   end Test_P;

   --  Old Process procedure
   --  procedure Process (Value : in out Integer)
   --  is
   --  begin
   --     Value := 10;
   --  end Process;

   --  What I hoped could work
   type Integer_A is access procedure (Value : Integer);

   procedure Process (Value : Integer_A) is
   begin
      Value (10);
   end Process;

   Item : Test_P.Test;

begin

   AIO.Put_Line (Item'Wide_Wide_Image);
   Item.Set_Member (2);
   AIO.Put_Line (Item'Wide_Wide_Image);
   --  Process (Item.Set_Member'Access);
   --                 ^--- This does'nt work             
end Bacasable;

For the moment, I only use a temporary variable Process (Temp) and then Item.Set_Member (Temp) but it’s quite ugly.

Am I missing something?

Thanks for your help.

Item.Set_Member is a “call of the procedure Set_Member”. Compiler unable to find function declaration with appropriate profile, thus reports error. It is hard to guess what happened from the error here.

Can you provide a bit more details what you want to do? There are few possible solutions, with access to subprogram, with generics…

Thanks for your reply.

In the library, I’m processing some JSON file to update a record.

Depending on the JSON field I’m currently reading, I was calling Process_Integer, Process_Date, Process_String_Vector and the likes with the corresponding field of the record as Item parameter. These procedures had signatures similar to:

procedure Parse_Integer
     (Reader  : in out VSS.JSON.Pull_Readers.JSON_Pull_Reader'Class;
      Item    :    out Long_Long_Integer;
      Success :    out Boolean)

I would have expected that Item.Set_Member'Access could be interpreted as an access to Test_P.Set_Member (T => Item, <other parameter>) as there’s no ambiguity (some sort of a closure).

The problem is your set member function has the signature similar to:
procedure Set_Member (T : in out Test; Value : Integer);

While your access type expects:
type Integer_A is access procedure (Value : Integer);

which is a totally different signature.

If all your objects are of type Test, then you can change it to:
type Integer_A is access procedure (T : in out Test; Value : Integer);

or if they are different but have the same base class, you can make Set_Member a base class operation, avoid the access types all together, and change process to:

   procedure Process (Source : in out Base_Class'Class) is
   begin
      Source.Set_Member(10);
   end Process;

if they aren’t the same base class, you can make an interface with Set_Member as primitive and adjust each of the types to implement the interface and again change process to:

   procedure Process (Source : in out Your_Interface'Class) is
   begin
      Source.Set_Member(10);
   end Process;

Full example:

with Ada.Text_IO; use Ada.Text_IO;


procedure jdoodle is

   package Test_Interfaces is
        type Integer_Test is limited interface;
        procedure Set_Member(Self : in out Integer_Test; Value : Integer) is abstract;
   end Test_Interfaces;
    
   package Test_P is
   
      use Test_Interfaces;

      --  Old record
      --  type Test is record
      --     Member : Integer := 0;
      --  end Test;

      --  New sealed record
      type Test is new Integer_Test with private;
      procedure Set_Member (T : in out Test; Value : Integer);

   private

      type Test is new Integer_Test with record
         Member : Integer := 0;
      end record;

   end Test_P;

   package body Test_P is

      procedure Set_Member (T : in out Test; Value : Integer) is
      begin
         T.Member := Value;
      end Set_Member;

   end Test_P;

   procedure Process (Source : in out Test_Interfaces.Integer_Test'Class) is
   begin
      Source.Set_Member (10);
   end Process;

   Item : Test_P.Test;
    
begin
   Process(Item);
end jdoodle;

Yes,

But, as I understand your proposal, Set_Member will be encoded in the Process procedures instead of passed as an argument. Thus Process will be specialized for that field of the record only.

Process_Integer, Process_Date and … are ‘generic’ in the sense that they don’t encode which specific field of the record they need to update (I may have twenty of them).

Here is an example of how it’s used (with all the if’s and Temp variables I need now).

while Success loop
   case Reader.Read_Next is
	when VSS.JSON.Streams.Key_Name =>
	   if Reader.Key_Name = "ID" then
	      Parse_Integer (Reader, Temp_Integer, Success);
	      if Success then
	         Item.Set_Id (Temp_Integer);
	      end if;
	   elsif Reader.Key_Name = "Name" then
	      Parse_String (Reader, Temp_String, Success);
	      if Success then
	         Item.Set_Name (Temp_String);
	      end if;
	   elsif Reader.Key_Name = "Description" then
	      Parse_String (Reader, Temp_String, Success);
	      if Success then
	         Item.Set_Description (Temp_String);
	      end if;
	   elsif …

I don’t really like the idea, but I can probably replace my procedures with functions returning the value and keep an out parameter for Success to avoid all the if.

Something like:

function Parse_String (Reader  : in out VSS.JSON.Pull_Readers.JSON_Pull_Reader'Class;
                       Item    : in     Virtual_String; 
                       Success : out Boolean)
   return Virtual_String;

and then I’ll just have to:

…
elsif Reader.Key_Name = "Name" then
   Item.Set_Name (Parse_String (Reader, Item.Get_Name, Success));
elsif Reader.Key_Name = "Description" then
…

The argument Item.Get_Name is used here to return the previous value in case of failure. Success is needed to end the loop.

It’s less verbose, but also less optimal as Set_Name is called even if nothing changes. And functions with out parameters are not ideal.

I guess what is confusing me on your question is why doesn’t the following work for you?

   type Integer_A is access procedure (Item : in out Test_P.Test; Value : Integer);

   procedure Process (Item: in out Test_P.Test; Value : Integer_A) is
   begin
      Value (Item, 10);
   end Process;
   AIO.Put_Line (Item'Wide_Wide_Image);
   Item.Set_Member (2);
   AIO.Put_Line (Item'Wide_Wide_Image);
   Process (Item, Test_P.Set_Member'Access);  

   AIO.Put_Line (Item'Wide_Wide_Image);

From your original post, I had assumed different types were hanging you up, but based on your last post, the types are all the same, just different member functions?

1 Like

Thanks! I was looking for something too complicated.

Passing Item and Set_Member as different parameters instead of a single Item.Set_Member is just simple.

I’ll probably have to add 'Class as I have at least two possible types for Item. But they live in the same hierarchy, so the solution is perfect for me.

No problem. Sorry it took a while to figure out.