I keep butting my head into the lack of an enumeration generics constraint.

I keep butting my head into the lack of an enumeration generics constraint.

I can’t make generic arrays or set types from an enumeration, nor do operations on a variable of that type.

(Disclaimer: The code is for illustration only and may be incorrect or illogical)

TTaskFactory = class

type

TTaskClassArray: Array [T] of TTaskClass; //<– illegal

TTaskSet: Set of T; //<– illegal

private

class var FFactory: TTaskClassArray;

protected

FAllowedTasks: TTaskSet;

public

class procedure RegisterTask(const Id: T; aTaskClass:TTaskClass);

function MakeTask(const Id:T):TTask;

property AllowedTasks read FAllowedTasks write FAllowdTasks;

end;

class procedure TTaskFactory.RegisterTask(const Id: T; aTaskClass:TTaskClass);

begin

Log(‘Registered Task Id ‘+IntToStr(Ord(ID))); //<– illegal

FFactory[Id] := aTaskClass; //<– illegal

end;

function TTaskFactory.MakeTask(const Id:T):TTask;

begin

if Id in AllowedTasks //<– illegal

then begin

if Assigned(FFactory[Id]) //<– illegal

then Result := FFactory[Id].Create //<– illegal

else raise EProgrammerNotFound.Create(‘Forgot to register’);

end

else Result := nil;

end;

If only I could write:

TTaskFactory = class

TSoTaskType = (ttMakeItSo, ttSoBeIt, … ttSoWhat)

TSoTaskFactory = class(TTaskFactory);

I am sure there are other examples, but I really miss being able to do

declarations and operations like these in a generic class:

Arrays, sets, low/high, ord, in, pred/succ, and related operations.

Yes, there are better ways to do class factories, and I use them – but the Enumeration constraint would enable so many possibilities.

57 thoughts on “I keep butting my head into the lack of an enumeration generics constraint.


  1. this looks more like java style coding than delphi coding, you could ditch the generic enumeration for say nativeint, and make a helper that converts nativeint into an enum type and back again, then take it from there.


    i feel like this requires a quote from rob pike: so, don’t do that


  2. So do I. but I think there is workaround in some cases, such as define TDictionary for the mapping, wrap some functions in TEnumHelper, TSetHelper, etc. Also, put a class constructor Create to check the type kind & fail fast. so it is still strong-typed and you can migrate them when primary type constraints are supported. 


  3. Dorin Duminica Which is what I do – with the risks that imply.  The constraint would allow for type safe code. What I miss the most when I do that, is High/Low.


  4. Please bear in mind that “how to do a factory” is not the issue here. The issue is the lack of a constraint that allows us to limit the type to an enumeration, and use constructs and operators on that type or types derived from it.


  5. Jeroen Wiert Pluimers The thing is that the restrictions generics have in .Net make sense, because being run-time instantiated they allow you to do certain things you cannot do with compile-time instantiation.


    But Delphi has all the restrictions run-time instatiation and none of the benefits of compile-time instantiation. It’s the worst of both worlds…


  6. Asbjørn Heid  Templates are a horrible system, which is why no one outside of C++ uses them.  (Good ideas get copied around and reused.)


    What makes you say that generic constraints are useful because of runtime advantages?  The advantages of constraints are at compile-time: they provide static typing to generics, rather than duck typing, (as is found in C++ templates,) which allows the compiler to easily and cleanly check for satisfiability up front, rather than spend an indefinite amount of time churning through template muck to figure out whether or not what you’re trying to do is valid.  (And then having a horrendously difficult time reporting what’s wrong, when it isn’t.)


  7. Mason Wheeler I said restrictions, not constraints.


    And I’m not familiar with another language that has compile-time generics, do you have some examples?


    edit: by compile-time generics I mean compile-time instantiated generics, like Delphi.


  8. Mason Wheeler That you can’t do template-like duck typing. I’m saying that restriction makes sense when the generics are instantiated at run-time, and instantiating the generics at runtime gives you some positives, for example the ability to instantiate based on a dynamic type (ie not compile-time-known), which counterbalances the restrictions.


    On the flip side, templates also have some restrictions due to being compile-time, but they also come the advantage of allowing for duck typing.


    Delphi have all the restrictions of run-time generics, but not the duck typing advantage of templates.


    Note I’m not saying Delphi needs touring complete templates… but if we have to live with the compile-time restrictions, we should get some form of duck typing in return.


  9. Asbjørn Heid  I think that’s the problem: looking at duck typing as an advantage.  To be perfectly honest, in all my years as a developer I have yet to find any scenario where the supposed “advantages” of duck typing even come close to outweighing the pain it causes even in normal development, let alone inside of a compile cycle!


  10. Mason Wheeler Maybe you’ve just been working on the wrong projects then. I found it very useful when I did C++ programming, and it’s something I still miss a lot in Delphi.


    As for pain, yeah sometimes it was a bit of a hassle to track down template errors, but I don’t duck typing itself is the primary reason for that, but rather a combination of touring complete templates and the rest of the C++ language allowing for quite a lot (compared to say Delphi).


    Maybe it could be a compiler option you’d have to enable, ala POINTERMATH, for those who prefer the current state.


  11. Mason Wheeler​ templates are used beyond C++. If love templates in Delphi for perf reasons. The fact that you don’t have a use for them, and have an illogical hatred of C++ doesn’t make templates useless.


  12. David Heffernan Templates absolutely destroy performance in C++: they can make a compile literally last forever, due to Turing-completeness, and that’s not even getting into the horrors inherent in trying to debug template code! As a developer, the massive productivity performance afforded by Delphi is far more valuable than whatever miniscule milliseconds templates can squeeze out of a program by inlining things here and there, saving a few cycles by removing the need to call some function. (On modern CPU architectures, do such things even have any noticeable impact at all anymore?)


  13. Mason Wheeler Allowing for duck typed generics in Delphi does not automatically mean the compile times would explode. C++ is a much more complicated language than Delphi.


  14. Mason Wheeler​ Compile time perf? I’m referring to runtime perf. How can you decide that’s not important to me? Your irrational and uneducated hatred of C++ is getting in the way of your reasoning.


  15. Mason Wheeler I miss C++ templates too. Sure there can be bad examples, but overall they are fantastically useful. Even a simple template class can be a lot more powerful than Delphi’s generics and a lot of that is due to the fact you can write code based on the type it’s meant for. You can’t do that in Delphi, as the enum example here illustrates. IMO that should compile and if a non-enum type was passed to the generic, then and only then should there be a compile error.


  16. Anyone who doubts the power and utility of templates should spend some time working with the eigen library and then consider how to implement same using generics.


  17. David Heffernan On the contrary, my education is precisely why I hate C++: I was forced to use it throughout my college years, and I learned far more about how it works (and how broken it is!) than you seem to believe.


  18. Mason Wheeler​ I guess you didn’t learn it well because most of what you write on the topic is wrong. Try eigen and then see how that would map to a language without templates.


  19. David Heffernan And what is “eigen”?  I’ve never heard anyone other than yourself ever even mention this library.  Whatever it is, it must not be all that significant…


  20. Mason Wheeler That’s your attitude in a nutshell. If you’ve not heard of it, it cannot be significant. How can you hope to learn anything new that way?


  21. David Heffernan That’s simple enough: when I hear enough people talking about something new, I know it’s significant enough to learn about.


  22. Mason Wheeler And how is that strategy working out for you. Your knowledge of C++ is woefully inadequate. Looks to me like it’s time to reassess.


  23. David Heffernan On the contrary, I know everything about C++ I will ever need to know: it inherited C’s buffer overflow problems, which are not easily fixed, which continually cause serious damage measured in billions of dollars every year, and therefore should not be used under any circumstances.


    That’s the only thing any developer needs to know about C or C++, because if you know that the language makes it easy to do the wrong thing, to do something so horrendously wrong that it can end up destroying your reputation and ruining the lives of your users (in the event of a major data breach, for example,) and makes it so difficult to do the right thing that the most experienced developers in the world continue to screw it up program after program, year after year, decade after decade, then literally nothing else matters.


  24. Lars Fosdal Personally I find inline variable declarations (with proper scoping), const correctness and “true” references to really help writing clear code, as in I find them really helpful when reading the code later, and I miss those a lot in Delphi.


    I will however agree that you can write some really god awful stuff in C++ if you try 🙂


  25. It’s easy to write bad code in any language. The idea that C++ is susceptible to buffer overruns and Delphi is not is quite frankly idiotic.


  26. David Heffernan Then why do buffer overruns only tend to happen in C/C++ code?  Heck, the only time I’ve ever run into one in Delphi code came from an improper declaration of external C DLL code.


    Sure, it’s possible to create a buffer overrun in Delphi, but in practice it doesn’t tend to happen.  In C, it’s easy, it’s the default behavior, and you have to go out of your way to do it right, while in Delphi you have to go out of your way to do it wrong.  Can you honestly say that’s not true?


  27. Mason Wheeler Yes, that is not true. You don’t need to go out of your way at all. Just read beyond the end of the buffer. And I’m talking about C++ which is a different language from C.


  28. Mason Wheeler How about his nice Delphi code. You can have buffer overruns in Delphi without going out of your way to do so.


    http://qc.embarcadero.com/wc/qcmain.aspx?d=119279


    http://qc.embarcadero.com/wc/qcmain.aspx?d=126740


    For those that cannot read private report it is about Windows GetClassName being called with wrong parameters throughout VCL and FMX potentially creating buffer overrun (Fixed in XE7, and been sitting there probably since Delphi 2009) 


    For example:


    function GetSysWindowClassName(Window: HWND): String;


    var


      sClassName: PChar;


    begin


      GetMem(sClassName, 256);


      try


        GetClassName(Window, sClassName, 256);


        Result := String(sClassName);


      finally


        FreeMem(sClassName, 256);


      end; 


    end;


  29. Dalija Prasnikar The use of PChar is going out of your way; in idiomatic Delphi code, we have a real string type that prevents this sort of problem.  This is an example of a problematic interoperation with external C code, as I mentioned earlier.


  30. Mason Wheeler​​ This demonstrates precisely my point about your lack of knowledge. If you knew C++ then you’d know that it has a string type. 


    But since, by your own statements, you’ve already made your mind up, how can you learn this?


  31. David Heffernan I know all about std::string.  I also know that it can’t be used in interop with external code.  I also know that, std::string or no std::string, we get buffer overflow problems in widely-used C++ code (such as operating systems) on a regular basis.


    When a string literal becomes a std::string rather than a char*, that would be real progress, but it can’t be done because backwards compatibility with decades of broken C code.


  32. Mason Wheeler You really have no idea what you are talking about. Operating systems are not written in C++. And std::string can be used in interop. The c_str() method is for that purpose. Or operator[] when writing to a string, although for that purpose I’d probably use std::vector.


    Well written C++ code is perfectly fine with literals because it never operates on them as C strings. For instance:


        std::string str(L”foo”);


    You are deeply prejudiced based on incomplete and inaccurate “knowledge”.


    Now, my original point was that a library like eigen is only possible due to the power of templates. It cannot be written with generics. 


    Whilst I personally much prefer writing code in Delphi, performance reasons make moving to C++ attractive. Were I to do so I would see very significant improvements in my app’s performance. It frustrates me that my chosen language holds me back. I want more.


  33. David Heffernan Where in the world did you get the idea that operating systems aren’t written in C++?  It’s all over the place in Windows, and most of the rest is C, which doesn’t even have a safe string.  The Linux kernel is C, and most of the userland stuff is C or C++.  And using .c_str() is not using std::string in interop, it’s using char* in interop and throwing conversions around everywhere because std::string can’t be used in interop.


    Same with your example of a constructor call that passes in a char*.  My point isn’t that those conversions aren’t available, but rather that they’re necessary, and if you don’t understand why that’s a bad thing, then it seems my knowledge is more complete and accurate than yours.

Leave a Reply