views:

300

answers:

3

I'm trying to write a generic cached property accessor like the following but am getting a compiler error when trying to check whether the storage variable already contains a value:

function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
begin
  if ADataValue = Default(T) then // <-- compiler error on this line
    ADataValue := ARetriever();
  Result := ADataValue;
end;

The error I'm getting is "E2015 Operator not applicable to this operand type".

Would I have to put a constraint on T to make this work? The help file says that Default() would accept anything except generic types. In my case I'm dealing mostly with simple types like String, Integer and TDateTime.

Or is there some other library function to perform this particular check?

I'm using Delphi 2009 in case that matters.


P.S.: Just in case it isn't clear from the code what I'm trying to do: In my case determining the actual property values might take a while for various reasons and sometimes I might not even need them at all. On the plus side however the values are constant so I only want to call the code that determines the actual value the first time that property is accessed and then store the value in a class field and the next time that property is accessed return the cached value directly. Here's an example of how I hoped I would be able to use that code:

type
  TMyClass = class
  private
    FSomeProp: String;
    function GetSomeProp: String;

    function GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
  public
    property SomeProp read GetSomeProp;
  end;

function GetSomeProp: String;
begin
  Result := GetProp<String>(FSomeProp,
                            function: String
                            begin
                              Result := SomeSlowOrExpensiveCalculation;
                            end);
end;

(obviously, there's more than just one property)

A: 

The problem is not the Default function, but the equality operator =.

You could constrain T to IEquatable and use the Equals method like this:

TMyClass = class
  function GetProp<T : IEquatable<T>>(var ADataValue: T; const ARetriever: 
end;
...
function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
begin  
if ADataValue.Equals (Default(T)) then
  ADataValue := ARetriever();  
Result := ADataValue;
end;
Smasher
Not sure why but I get an "Undeclared identifier 'IEquatable'" with that code (even though I can see `IEquatable` is indeed declared in System.pas). I would however doubt this would work for me anyways as I am not aware that primitive types like `String`, `TDateTime` or `Integer` somehow implicitly support that interface...
Oliver Giesen
@Oliver: you're right, you can't use that for primitive types. Unfortunately, I am not aware of another solution. Let's see if others are able to help. I don't know why you get an error message when using `IEquatable`.
Smasher
Actually, what is declared in System.pas is not `IEquatable` but `IEquatable<T>` which explains the error... `GetProp<T: IEquatable<T>>(...` does compile but then, as expected, I get the error when passing `String` for `T`: "Type parameter 'T' must support interface IEquatable<System.string>"
Oliver Giesen
+1  A: 

As I understand things it looks like u want to do some kind of memoize functionality. If this is the case just read this article

http://blogs.teamb.com/craigstuntz/2008/10/01/37839/

Binis
Thanks! This is indeed very similar to what I'm trying to do but Craig's implementation would result in too big an unnecessary overhead in my particular case because it would instantiate one new `TDictionary` instance for every property... It's unfortunately also not an answer to the original question of how to test whether a generically typed variable has a non-default value or not, so given that context I'm afraid I couldn't even upvote your answer. :(
Oliver Giesen
You could avoid this overhead by using something like a `TCacheManager`. The cache manager has one dictionary. Just use something like `IntToStr (Integer (Pointer (Self))) + '.Property1'` as the key and register your cached values.
Smasher
Well the only "legal" way to compare generics is to do what Delphi does in Generics.Collections.pas. Take a look at QuickSort implementation.
Binis
You can aways do the followingfunction TMyClass<T>.IsEqualWithDefault(Item: T): boolean;var def : T;begin def := Default(T); result := CompareMem(@Item, @def, SizeOf(T));end;but I doubt it will suit your case
Binis
@Smasher: The main problem I see with using a dictionary for my use case is that a dictionary would have to have a fixed value type while I need multiple result types. BTW: Is `TCacheManager` something that is already available somewhere? I haven't been able to find that in the VCL sources...
Oliver Giesen
@Binis: Thanks for the hint on Generics.Collections! I got it working now: `if TComparer<T>.Default.Compare(ADataValue, Default(T)) = 0 then`If you put this in an answer I would upvote and accept it.
Oliver Giesen
or, even slightly better: `if TEqualityComparer<T>.Default.Equals(ADataValue, Default(T)) then`
Oliver Giesen
+1  A: 

After a hint in the comments from Binis and digging around a little in Generics.Collections I came up with the following which appears to work just as I wanted it:

function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
var
  lComparer: IEqualityComparer<T>;
begin
  lComparer := TEqualityComparer<T>.Default;
  if lComparer.Equals(ADataValue, Default(T)) then
    ADataValue := ARetriever();
  Result := ADataValue;
end;
Oliver Giesen