Using own class as a type parameter constraint in class declaration
Asked Answered
W

1

9

I have the following declaration of a class in Delphi XE8:

TestClass = class;
TestClass = class
  function test<T: TestClass>(supplier: TFunc<T>): T; // Compiler error
end;

Which throws the following compiler error:

E2086 Type 'TestClass' is not yet completely defined

When I add another class to the mix and use that one as constraint instead, it works fine:

AnotherTestClass = class
end;

TestClass = class;
TestClass = class
  function test<T: AnotherTestClass>(supplier: TFunc<T>): T; // No Error
end;

I suspect the problem is that the forward type declaration does not tell Delphi enough about the TestClass type yet. This is perhaps more obvious since the following attempt to work around the problem throws the very same compiler error on a different line:

TestClass = class;
AnotherTestClass = class (TestClass) // Compiler Error
end;
TestClass = class
  function test<T: AnotherTestClass>(supplier: TFunc<T>): T;
end;

Am I doing something wrong and if not, is there a way around this problem?

Wastepaper answered 5/1, 2016 at 11:41 Comment(4)
It seems that this is a bug in the compiler so I have submitted a bug report here.Wastepaper
Certainly the code "TestClass = class; AnotherTestClass = class (TestClass) // Compiler Error end;" should produce the error shown because Delphi is a single pass compiler. But I can see no reason why the same should apply to the construct you are trying to create. Enough information is available at the time for the definition to be valid.Cheesecloth
@Cheesecloth agreed, but since the compiler error is the same, I figured it's a hint at what's going wrong. This does introduce a problem however, what if I want two classes who use eachother as a type parameter constraint?Wastepaper
I think the same issue applies to your two classes using each other as type parameter constraints. Obviously the two classes need to be in the same unit and same type block just as if the classes referenced each other, but I imagine that you will hit the same problem. And just like your example, I see no reason why the compiler should not allow allow it.Cheesecloth
B
8

You are not doing anything wrong. What you are attempting should be possible, but the compiler is, in my view, defective. There's no viable way to work around this without completely changing the design. One way to work around the issue would be to enforce the constraint at runtime. However, that would count, in my eyes, as completely changing the design.

Note that in .net what you are trying to do is perfectly possible:

class MyClass
{
    private static T test<T>(Func<T> arg) where T : MyClass
    {
        return null;
    }
}

The Delphi generics feature was based on .net generics and I rather suspect that the problem you face is down to an oversight on the part of the Delphi developers.

You should submit a bug report / feature request.

Update 1

LU RD suggests a better workaround. Use a class helper:

type
  TestClass = class
  end;

  TestClassHelper = class helper for TestClass
    function test<T: TestClass>(supplier: TFunc<T>): T;
  end;

This will allow you to have the constraint tested at compile time. However, it does force you to define the method outside the function which is untidy, and it stops you using a class helper for any other purpose. So, you should still submit a bug report / feature request in my view.

Update 2

Bug report: RSP-13348

Babi answered 5/1, 2016 at 11:45 Comment(5)
Moving the function to a class helper compiles: ` TestClassHelper = class helper for TestClass function test<T: TestClass>(supplier: TFunc<T>): T; end; `.Diaconal
@LURD Good catch, that's actually a viable workaround. The behaviour and API of the class should be identical to what was intended, right? It's still a major oversight in teh compiler though, so I'll still submit a bug report.Wastepaper
Please post a link to your bug report here. People may vote for it if they want the generics features to be more orthogonal and complete.Loosing
@WarrenP I was just on it :) Here's the bug report. Be warned, I've never written a feature request before.Wastepaper
A better workaround in my opinion, is to use a abstract base class for your constraint. This would make it easier to mock a TestClass. Add whatever virtual abstract) functions constitute the type signature.Loosing

© 2022 - 2024 — McMap. All rights reserved.