13

The following declaration:

type
  TRec = record
    constructor Create;
  end;

produces this compilation error:

E2394 Parameterless constructors not allowed on record types

The documentation rather unhelpfully states:

No further information is available for this error or warning.

My question is why the language was designed this way. Was it done this way purely to echo the analogous restriction for C# structs?

The language guide says this:

Records are constructed automatically, using a default no-argument constructor, but classes must be explicitly constructed. Because records have a default no-argument constructor, any user-defined record constructor must have one or more parameters.

But that doesn't make much sense. If there is a default constructor for a record, it can't be found through RTTI. And even if there was, why would that imply that it was impossible to add another one? You can do so for classes.

Perhaps the rationale is that if we were allowed to define our own parameterless constructors, we'd expect the compiler to call them automatically.

Note: I understand that you can use a parameterless static class function as a workaround. Indeed, I personally always prefer to use static class function instead of record constructors. But that's not the point of the question. What I really want to know is why parameterless constructors are not allowed on record types.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Your guess makes sense as IIRC this was introduced for the now dead Delphi .NET, but I could be remembering wrongly –  Sep 02 '17 at 08:49
  • 1
    From what I've read: the CLR supports it. VB.Net supported it. Delphi.Net could've supported it. The reasons that I see listed for doing it like this in C# don't really apply to Delphi or the way Delphi developers think. MS tried adding it to C#6 but ran into backwards compatibility issues and rolled back. All in all: good question. Why doesn't Delphi support it? – Wouter van Nifterick Sep 02 '17 at 10:28
  • 1
    @WoutervanNifterick If I was guessing I'd say that Emba copied C# without really thinking about it. As you point out, .net doesn't block it, it's just a language decision in C#, not driven by the platform. – David Heffernan Sep 02 '17 at 10:38
  • 1
    There is this : [*Records are constructed automatically, using a default no-argument constructor, but classes must be explicitly constructed. Because records have a default no-argument constructor, any user-defined record constructor must have one or more parameters.*](http://docwiki.embarcadero.com/RADStudio/en/Structured_Types#Records_.28advanced.29) Although there is clearly no accessible default parameterless constructor for any record. Maybe the compiler reserves this entry internally? – J... Sep 02 '17 at 10:51
  • 1
    Or maybe this is a last trace of the short-lived C#builder? A deliberate attempt to let Delphi evolve towards C#? Anyway, I share the suspicion that this was never given much thought. I'd even say that in some situations it makes more sense to REQUIRE a parameterless constructor than to forbid it. – Wouter van Nifterick Sep 02 '17 at 11:10
  • 1
    @J... "Records are constructed automatically.." except this is not actually true. Only records with managed types are initialized automatically, and even then only managed types inside are initialized, all other values are not. – Dalija Prasnikar Sep 02 '17 at 11:18
  • 1
    @DalijaPrasnikar Yes, I know. I was just quoting the documentation as a reference. In any case, it **is** a behaviour and, while it isn't accessible through RTTI, it could be the case that this default initialization behaviour is taking up the space of a default parameterless constructor in the compiler. I'm not sure anyone but the Emba compiler team can answer this. – J... Sep 02 '17 at 11:25
  • 2
    Even this isn't allowed : `constructor Create(x:double = 1.0);` -- [E2471 Possibly parameterless constructors not allowed on record types](http://docwiki.embarcadero.com/RADStudio/E2471_Possibly_parameterless_constructors_not_allowed_on_record_types_(Delphi)) – J... Sep 02 '17 at 11:30
  • @DavidHeffernan That said, a normal overload can't do this either (ie: optional parameters that, when omitted, reduce to the same signature as another method with the same name). It's clearly a special case, however, since the default constructor, if it exists, is only accessible by the compiler itself (hence the special warnings) but it behaves otherwise like records do already have a hidden constructor with no arguments reserved for internal use, probably for managing initialization of compiler-managed types within the record. Best guess I can come up with. – J... Sep 02 '17 at 11:36
  • 2
    @J.. Your example constructor is not an overload though. I don't think there is a hidden internal constructor. I think the documentation is fiction. – David Heffernan Sep 02 '17 at 11:37
  • @DavidHeffernan It reduces to the same thing. If you try it with overloads the compiler stops on possibly degenerate parameter lists before objecting to the lack of overload decoration. – J... Sep 02 '17 at 11:40
  • @J.. I get that. The comparison is not fair in my view. Nobody said anything about overload. So compare with normal methods that are not overloaded. – David Heffernan Sep 02 '17 at 11:42
  • @DavidHeffernan Not sure what you're getting at... that is the behaviour for normal methods. – J... Sep 02 '17 at 11:44
  • @j.. Sure you can. One hides the other, in classes. But who said anything about the same name. Call the constructor foo if you like. – David Heffernan Sep 02 '17 at 11:46
  • 1
    @DavidHeffernan But records aren't classes - they don't have a VMT. You can't override or reintroduce or whatever. And, if we assume there is a hidden constructor that is responsible for initializing managed types then it would be a mess to allow a user-substituted method because you would need to take over the responsibility of initializing things like strings. – J... Sep 02 '17 at 11:49
  • 1
    @J.. The guesswork isn't doing a lot for me. My point is that your comparison with overloads was misleading. – David Heffernan Sep 02 '17 at 11:51
  • @DavidHeffernan I don't think so at all. If we assume a hidden constructor exists (is automatically generated by the compiler for each record type) then it must belong to *the record type itself*, not an ancestor. We're talking about a duplicate definition within a single type, not a derivative type - this is exactly an overload type of situation. In any case, it is completely guesswork - that's why these are comments and not answers. – J... Sep 02 '17 at 11:52
  • 2
    @j.. Not at all because I can use any name I like for my constructor. – David Heffernan Sep 02 '17 at 11:55
  • 1
    @DavidHeffernan Ah. I see your point. – J... Sep 02 '17 at 11:58
  • @David: In Delphi we can, but I am beginning to suspect this is because of some issue of compatibility with C++Builder. As you know, in C++, contructors can't have (different) names, and constructors are simply named after their type. So even a parameterless constructor Foo would be a problem. That is also why in some parts of the Delphi RTL, they prefer overloaded constructors Create. That way you won't have constructor CreateCentimeters(Integer) and constructor CreateMilimeters(Integer) at the same time. Works in Delphi, but not in C++Builder. Hmm... I'm not sure if I made myself clear here. – Rudy Velthuis Sep 02 '17 at 12:05
  • 3
    @J... Point is there is no default constructor at all. There is only _InitializeRecord helper called before record variable gets into scope if the record contains managed types. You cannot hide something that does not exists. – Dalija Prasnikar Sep 02 '17 at 12:42
  • I deleted my answer, but maintain that a parameterless constructor (or one with only default arguments) would probably interfere with C++'s default constructor (I guess C++ would not generate an implicit one anymore, and that could somehow interfere with the _InitiliazeRecord mechanism). But that is just a guess. The fact that a constructor with only default arguments is not allowed either looks a lot like how [cppreference](http://en.cppreference.com/w/cpp/language/default_constructor) describes default constructors. – Rudy Velthuis Sep 02 '17 at 16:47
  • FWIW, the fact that a parameterless constructor with a different name (e.g `Init`) is not allowed either could also point to conflicts with C++ (where constructors can't have a name of their own). But it could also point to C# heritage (which also doesn't allow named constructors). – Rudy Velthuis Sep 02 '17 at 16:58
  • 1
    Also, the fact that, if, in C++, you declare an array like `Foo elements[1000];`, the default constructor is called for each element (I just tested that), while this doesn't happen (and shouldn't happen) in Delphi seems to be another conflict with C++ (and C#). – Rudy Velthuis Sep 02 '17 at 17:14
  • Rio has a webpage reated to this: http://docwiki.embarcadero.com/RADStudio/Rio/en/E2471_Possibly_parameterless_constructors_not_allowed_on_record_types_(Delphi) - It doesn't answer the question. But seems to confirm a compiler bug. ANYWAY, it doesn't solve anything under Delphi XE7. – Gabriel Nov 10 '19 at 11:50

1 Answers1

4

I can't give you a definitive answer (only the compiler builders can), but I suspect it is not related to Delphi's .NET past, but rather to Delphi's relation with C++Builder.

As cppreference says:

A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter).

C++ allows for parameterless constructors, and these parameterless constructors would become the default constructor, in C++. A default constructor is called in many situations, e.g. if you simply declare:

Foo myFoo;

The default constructor is called. This does not happen in Delphi, but a C++ programmer might expect it. Similarly, if you do:

Foo elements[1000];

The default constructor is called on each element (I checked that). This also doesn't happen in Delphi, although a C++ programmer might expect it.

Other hints that this is C++-related:

  • Constructors with different names (e.g. Init) are not allowed either. This seems to point to conflicts with C++ or with C#, as in both, constructors have the name of the class or struct, so any parameterless constructor would be mapped to Foo() (in a struct or class called Foo.)
  • Constructors with only default parameters are not allowed either. This matches the cppreference description for default constructors with only default arguments.

All in all, there are hints that parameterless constructors (or ones with only default parameters) conflict with C++ (i.e. C++Builder) and that that is why they are not allowed.

Note that this is not the only restriction caused by differences with C++: e.g. in Delphi you can't cast integers to and from floating point types either, because in C and C++, that would cause a conversion, while in Delphi, it would merely cause a reinterpretation of the bits. In order not to confuse people who were coming to Delphi from C or C++, the casting restriction was placed on floating point types. There may be more.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • 2
    C++ allows for parameterless constructors – David Heffernan Sep 02 '17 at 13:32
  • 1
    That last comment about casting numeric types can't be right. That's not the reason. – David Heffernan Sep 02 '17 at 17:51
  • Yes, that is the reason. I have this directly from the people at (then) Borland, when I was there for a TeamB conference. It was either Chuck Jazdzewski or Danny Thorpe who explained it that way (both were in the room). Actually, that restriction did not always exist. It was introduced when C++Builder was introduced. – Rudy Velthuis Sep 02 '17 at 17:54
  • 1
    Can't be true. The two languages can and do have different rules. Interop is the concern where one influences another. There is no interop in casting of numeric types. – David Heffernan Sep 02 '17 at 18:10
  • @David: you will have to believe me that it **is** true. As I said, I have it from Borland R&D directly. Floating point casts are not allowed in Delphi because of the differences, and because C and C++ people (whom they tried to attract to Delphi too) **would expect** a conversion. I also know there was disagreement with this decision, but that was the stated reason. Actually, what other reason could there be? It worked before, in Delphi 1 and 2, IIRC. And indeed, that was not for interop reasons. – Rudy Velthuis Sep 02 '17 at 18:27
  • 1
    I don't care if the Pope told you, it's just utter nonsense! They might have made a decision for marketing reasons, but your text implies a technical reason. That's clearly nonsense. – David Heffernan Sep 02 '17 at 18:29
  • That was a marketing reason (i.e. in order not to confuse people switching from C or C++), sure. I never claimed otherwise. – Rudy Velthuis Sep 02 '17 at 18:33
  • 1
    No. You said it was due to incompatibility with C++. Impossible to construe that as marketing reasons. – David Heffernan Sep 02 '17 at 18:34
  • Yes, that is an incompatibilty with C and C++. OK, I reworded it a little (used the term "difference"). They did more things in order not to confuse people attracted from other languages, also not really necessary for technical reasons, like zero-based strings, the deprecation of pointers in the mobile compilers, ARC, etc. – Rudy Velthuis Sep 02 '17 at 18:43