Reputation: 514
I am trying to create an Ada generic package that has a subprogram parameter with a default value. I cant get the compiler to recognize the default value.. Im guessing this is due to visibility. Is there a way to forward declare a function within the generic declaration?
The Generic Spec:
generic
type Item is private;
type Item_Ref is access all Item;
Addr : System.Address;
Default : Item;
with Is_Valid (Obj : Item) return Boolean;
-- Forward Declare ** DOES NOT COMPILE
function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
with function Validate (Data_Ptr : Item_Ref) return Boolean is Default_Validate;
package Foo is
-- function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
function Read_Eeprom return Item;
end Foo;
The Generic Body:
package body Foo is
Obj : aliased Item;
for Obj'Address use Addr;
-- Read Method
function Read_Eeprom return Item is
begin
-- ** Read EEPROM using OBJ **
Validate (Obj'Unchecked_Access);
end Read_Eeprom;
-- Default Validate Method
function Default_Validate (Data_Ptr : Item_Ref) return Boolean is
Valid : Boolean;
begin
Valid := Is_Valid(Data_Ptr.all);
if not Valid then
Data_Ptr.all := Default;
end if;
return Valid;
end Default_Validate;
end Foo;
Driver
with Foo;
procedure Main is
MAX_INT : constant Integer := 100;
MIN_INT : constant Integer := 0;
-- Special / Non-Scaler Type
type Pair_Type is
record
X : Integer;
Y : Integer;
end record;
type Pair_Ref is access all Pair;
-- Is Valid
function Int_Is_Valid(Int : Integer) return Boolean is
begin
return (Int <= MAX_INT and Int >= MIN_INT);
end Pair_Is_Valid;
-- Is Valid
function Pair_Is_Valid(Pair : Pair_Type) return Boolean is
begin
return Pair.X'Valid and Pair.Y'Valid;
end Pair_Is_Valid;
-- Validate
function Pair_Validate(Pair : Pair_Ref) return Boolean is
Valid : Boolean := True;
begin
if not Pair.X'Valid then
Pair.X := 0;
Valid := False;
end if;
if not Pair.Y'Valid then
Pair.Y := 0;
Valid := False;
end if;
return Valid;
end Special_Validate;
type Int_Ref is access all Integer;
My_Int : Integer;
My_Pair : Pair_Type;
Default_Pair : Pair_Type := (0,0);
package Int_Obj is new Foo (Item => Integer,
Item_Ref => Int_Ref,
Addr => My_Int'Address,
Default => 0,
Is_Valid => Int_Is_Valid);
package Pair_Obj is new Foo (Item => Pair_Type,
Item_Ref => Pair_Ref,
Addr => My_Pair'Address,
Default => Default_Pair,
Is_Valid => Pair_Is_Valid,
Validate => Pair_Validate);
Tmp_Int : Integer;
Tmps_Pair : Pair_Type;
begin
Tmp_Int := Int_Obj.Read_Eeprom;
Tmp_Pair := Pair_Obj.Read_Eeprom;
end Main;
The error Im getting is "end of file expected, file can only have one compilation unit" How can I default a generic subprogram to a function that is a member of the package?
Upvotes: 1
Views: 2228
Reputation: 4198
You really keep moving the goalposts. Your new additions are, in a word, terrible: they don't compile at all and they are a smattering of obviously cut-and-pasted code as evidenced by mismatching function-names (e.g. Int_Is_Valid
/Pair_Is_Valid
).
First, let's use a signature-package.
signature.ads
generic
Type Item is private;
Default : in Item;
package SIGNATURE is
end SIGNATURE;
foo.ads
with
System,
SIGNATURE;
generic
with package Item_Pkg is new SIGNATURE(<>);
Addr : System.Address;
with function Is_Valid(X : Item_Pkg.Item) return Boolean is <>;
package Foo is
use Item_Pkg;
function Read_Eeprom return Item;
function Is_Valid (Data_Ptr : access Item) return Boolean;
private
Port : Item;
pragma Volatile( Port );
Pragma Import( Convention => Ada, Entity => Port );
For Port'Address Use Addr;
end Foo;
foo.adb
package body Foo is
function Read_Eeprom return Item is
Result : constant Item:= Port;
begin
if Is_Valid(Result) then
return Result;
else
return Default;
end if;
end Read_Eeprom;
function Is_Valid (Data_Ptr : access Item) return Boolean is
begin
return Is_Valid(Data_Ptr.all);
end Is_Valid;
end Foo;
driver.ads
package Driver is
MAX_INT : constant Integer := 100;
MIN_INT : constant Integer := 0;
-- Special / Non-Scaler Type
type Pair_Type is
record
X : Integer;
Y : Integer;
end record;
-- Is Valid **USING OVERLOADS**
function Is_Valid(Int : Integer ) return Boolean;
function Is_Valid(Pair : Pair_Type) return Boolean;
My_Int : Integer;
My_Pair : Pair_Type;
private
Default_Pair : constant Pair_Type := (0,0);
Default_Integer : constant Integer := 0;
end Driver;
driver.adb
with
Foo,
SIGNATURE;
package body Driver is
-- Is Valid
function Is_Valid(Int : Integer) return Boolean is
(Int <= MAX_INT and Int >= MIN_INT);
function Is_Valid(Pair : Pair_Type) return Boolean is
(Pair.X'Valid and Pair.Y'Valid);
package Int_pkg is new SIGNATURE(Integer, 0);
package Pair_Pkg is new SIGNATURE(Pair_Type, Default_Pair);
-- Using defaults for Is_Valid.
package Int_Obj is new Foo (Item_Pkg => Int_Pkg,
Addr => My_Int'Address
);
package Pair_Obj is new Foo(Item_Pkg => Pair_Pkg,
Addr => My_Pair'Address
);
end Driver;
Given the rather obvious structuring of your code around access
-types, I'm guessing that you are trying to "import" knowledge from C or C++. That's going to cause you a lot of trouble/work later on if you try using Ada like it's a C-style language.
Also, it may be beneficial to sit down, take a breath, and think about things in terms of types both the problem-space and, separately, the architecture.
Upvotes: 1
Reputation: 4198
I'm not sure this works either. It would seem I would have to define a validate function for each instance of a generic even if I'm not passing it in as a parameter.
That is probably the best way to approach this; you're talking validation of data after all... and defaulting that could have unexpected behavior. (Any type that's crossing in/out of your system ought to be validated; e.g. File reads, DB reads, user-input, etc.)
The point of having the default function in the generic was to eliminate duplicate code. Any other thoughts?
Well, there is one method that springs to mind. It requires us to limit what we accept in our generic though. My other solutions are [almost] fully generalized and therefore would work on any non-limited type.
Taking the attribute 'Valid
we see that the documentation says this:
The Valid attribute can be used to check the validity of data produced by unchecked conversion, input, interface to foreign languages, and the like.
So we have a default validation, of sorts.
We also have a way to have attributes stand in for the default of function formal-parameters.
We have a way to default to visible functions.
Last we have nested generics.
Now there's no generic formal type scalar
, which would be useful here... but we have a way to limit the type of a formal parameter Type T(<>) is (<>)
is limited to integral numerics, modular-types, and enumerations... all of which we know are scalar-types.
Generic
Type Item(<>) is (<>);
Package Generic_Base is
-- To use this as a Ada-95 pkg delete everything after 'Boolean',
-- create a body and return X'Valid from the implementation.
Function Default_Validate(X : Item) Return Boolean is (X'Valid);
Generic
with function Validate(X:Item) return Boolean is Default_Validate;
Package Generic_Nested is
-- Operations requiring validation.
End Generic_Nested;
End Generic_Base;
Using these packages would be as follows:
Package Base is new Generic_Base( Integer );
Package Nested is new Base.Generic_Nested;
If you're happy in limiting the formal parameter to integrals/modulars/enumerations, this should work.
So, there you go.
Recommended reading:
No syntax highlighting because it looks terrible on this.
Upvotes: 1
Reputation: 4198
As you have it, the generic is defining a function, Default_Validate
, because keyword function
is not preceded by with
. What you should have is this:
generic
type Item is private;
type Item_Ref is access all Item;
with function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
-- A function "Validate", which defaults to 'Default_Validate'.
with function Validate (Data_Ptr : Item_Ref) return Boolean is Default_Validate;
package Foo is
-- function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
end Foo;
EDIT:
The comments clarified that the previous was not what you want. The form above would be used in cases where you have a possibly-overriding validator and a default one w/o having to resort to using tagged types (Ada's terminology for instances of OOP classes).
Instead what you want is for the parameters to possibly take subprograms from the visible subprograms as defaults for the formal parameters, the following does so:
generic
type Item is private;
type Item_Ref is access all Item;
-- A function "Validate", which defaults to 'Validate'. The function
-- needs to be visible when the generic is instantiated, not here
-- where the generic is defined.
with function Validate (Data_Ptr : Item_Ref) return Boolean is <>;
package Foo is
-- Just a stub.
end Foo;
Type Some_Integer_Access is access all Integer;
function Validate (Data_Ptr : Some_Integer_Access) return Boolean is (true);
Package K is new Foo( Item => Integer,
Item_Ref => Some_Integer_Access
);
But even this might be improved:
generic
type Item is private;
with function Validate (Data_Ptr : not null access Item) return Boolean is <>;
package Foo is
-- Just a stub.
end Foo;
function Validate (Data_Ptr : not null access Integer) return Boolean is (true);
-- One parameter! Ah! Ah, Ah!
Package K is new Foo( Item => Integer );
Upvotes: 1
Reputation: 31699
Unfortunately, you can't--it's a chicken-and-egg problem. The compiler needs to figure out what all the generic parameters are going to be before it can instantiate the generic; but the Default_Validate
method will not become available until after the generic is instantiated. The closest I think you can come is to declare two generics:
generic
type Item is private;
type Item_Ref is access all Item;
with function Validate (Data_Ptr : Item_Ref) return Boolean;
package Foo is
function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
-- etc.
end Foo;
generic
type Item is private;
type Item_Ref is access all Item;
package Foo_With_Default_Validator is
-- important procedure/function declarations from Foo
end Foo_With_Default_Validator;
package body Foo_With_Default_Validator is
function Default_Validate (Data_Ptr : Item_Ref) return boolean;
package My_Foo is new Foo(Item, Item_Ref, Default_Validate);
function Default_Validate (Data_Ptr : Item_Ref) return boolean
renames My_Foo.Default_Validate;
-- and other procedures/functions will be renames of things from My_Foo
end Foo_With_Default_Validator;
(I haven't tested this yet. EDIT: tested, compiles OK.) I'm assuming here that the only publicly visible things in Foo are procedures and functions. If there are other important features (such as types), it gets more complicated, and then you might have to use nested generics, where with function Validate
gets moved off the outer generic into an inner generic, or you might be able to use a generic formal package to split the generic into two parts. In either of those cases, the user of the generic might have to perform two instantiations. If the above solution works, then a user will instantiate either Foo
or Foo_With_Default_Validator
, but it would be one or the other--two instantiations wouldn't be needed. If you need more assistance, I think we'd need to see the visible part of Foo
.
EDIT 2: Here's a solution if you're willing to require an 'Access
attribute when instantiating:
generic
type Item is private;
type Item_Ref is access all Item;
Validate : access function (Data_Ptr : Item_Ref) return Boolean := null;
package Foo is
function Default_Validate (Data_Ptr : Item_Ref) return Boolean;
-- etc.
end Foo;
Then in the body of Foo
, you'll want a function like this:
function Perform_Validate (Data_Ptr : Item_Ref) return Boolean is
begin
if Validate = null
then return Default_Validate (Data_Ptr);
else return Validate (Data_Ptr);
end if;
end Perform_Validate;
and call Perform_Validate
from the rest of the body whenever you want to call the validation function. (Perform_Validate
could be written more concisely using new Ada 2012 features, but you get the idea.)
Upvotes: 1