I want to check to see if a generic type is "valid" without using constraints.
In other words, I want to write the following code:
class function TMaybe<T>.FromValue(aValue: T): TMaybe<T>;
begin
if T <> nil then
begin
Result := TMaybe<T>.Some(aValue);
end else
begin
Result := TMaybe<T>.None;
end;
end;
However, this doesn't compile with the error:
E2571 Type parameter 'T' doesn't have class or interface constraint
Obviously for a class like this, I'd like to be able to have any type be a TMaybe.
Is there a way to check to see if contraint-less type is "valid", that is, not null? (I don't care about empty strings, etc.)
Should I write a TypeIsEmpty<T>(aValue: T): Boolean
that uses TypInfo to figure it out? I'd like to avoid that.
Try something like this:
type
PMethod = ^TMethod;
class function TMaybe<T>.FromValue(aValue: T): TMaybe<T>;
begin
{
the *undocumented* IsManagedType() intrinsic function returns True if T
is an interface, string, or dynamic array, but it also returns True if T
is a record containing such a field. Since a record can't be compared to
nil, IsManagedType(T) is not useful here.
Using the *undocumented* GetTypeKind() intrinsic function can be used
instead to handle ONLY nil-able types...
}
// if IsManagedType(T) then...
case GetTypeKind(T) of
tkString, tkClass, tkLString, tkWString, tkInterface, tkDynArray, tkUString:
begin
if PPointer(@aValue)^ = nil then
Exit(TMaybe<T>.None);
end;
tkMethod:
begin
if (PMethod(@aValue)^.Data = nil) or (PMethod(@aValue)^.Code = nil) then
Exit(TMaybe<T>.None);
end;
end;
Exit(TMaybe<T>.Some(aValue));
end;
Intrinsic functions like GetTypeKind()
(and IsManagedType()
) are evaluated at compile-time, and as such code branches that evaluate to False
are optimized out of the final executable. However, as @DavidHeffernan mentions in comments, the compiler checks the code syntax before it instantiates the generic, hence the PPointer
typecast to get around that.
So, if you set T
to a nil-able type, like a String, the compiler will be able to optimize the code down to this:
class function TMaybe<String>.FromValue(aValue: String): TMaybe<String>;
begin
if Pointer(aValue) = nil then
Exit(TMaybe<String>.None);
Exit(TMaybe<String>.Some(aValue));
end;
And if you set T
to a non nil-able type, like an Integer, the compiler will be able to optimize the code down to this instead:
class function TMaybe<Integer>.FromValue(aValue: Integer): TMaybe<Integer>;
begin
Exit(TMaybe<Integer>.Some(aValue));
end;