Jump to page: 1 2
Thread overview
Allow this() { } for structs
Nov 02, 2020
Q. Schroll
Nov 06, 2020
FeepingCreature
Nov 06, 2020
IGotD-
Nov 06, 2020
Q. Schroll
Nov 06, 2020
IGotD-
Nov 07, 2020
Q. Schroll
Nov 07, 2020
IGotD-
Nov 07, 2020
FeepingCreature
Nov 07, 2020
IGotD-
Nov 07, 2020
Paul Backus
Nov 07, 2020
FeepingCreature
November 02, 2020
In metaprogramming, I find myself writing stuff like

    struct S(Ts...)
    {
        this(Ts args)
        {
            static foreach (T; Ts) { /*...*/ }
        }
    }

and get compiler errors for the case that `Ts` is empty. So I need an actually useless

    static if (Ts.length != 0)
    this(Ts args) { /*...*/ }

Can we just allow this() when the body is essentially empty (that is: after rewrites and lowerings contains no statements)? The compiler would happily accept the code and pretend this() { } isn't there. It would even reduce confusion because S() is legal as an expression, but this() isn't as a constructor. With that, this() is legal as a constructor, but it must not do anything.
November 06, 2020
On Monday, 2 November 2020 at 19:53:31 UTC, Q. Schroll wrote:
> Can we just allow this() when the body is essentially empty (that is: after rewrites and lowerings contains no statements)? The compiler would happily accept the code and pretend this() { } isn't there. It would even reduce confusion because S() is legal as an expression, but this() isn't as a constructor. With that, this() is legal as a constructor, but it must not do anything.

I just want this() always, even with a non-empty body.

We run into an issue where we need predictable destructor calls for a struct constructed for a with(), but we also want possibly immutable data in the struct, so we *have* to use this() rather than static opCall() which is the standard workaround.

So we end up with code like this:

struct StructType {
  this(int) { ... }
}

StructType structType() { return StructType(0); }

I do not understand what purpose the lack of this() serves. I mean, I understand what purpose it serves, it's so T.init is always equivalent to T(), but I don't understand what purpose *that* serves. T.init is *already* not a valid constructed T if you want to use any invariants and/or call any methods at all. We *already* can't treat them equivalently regardless, so from my perspective the ban on T() serves no purpose.
November 06, 2020
On Friday, 6 November 2020 at 09:54:23 UTC, FeepingCreature wrote:
> On Monday, 2 November 2020 at 19:53:31 UTC, Q. Schroll wrote:
>> Can we just allow this() when the body is essentially empty (that is: after rewrites and lowerings contains no statements)? The compiler would happily accept the code and pretend this() { } isn't there. It would even reduce confusion because S() is legal as an expression, but this() isn't as a constructor. With that, this() is legal as a constructor, but it must not do anything.
>
> I just want this() always, even with a non-empty body.
>
> We run into an issue where we need predictable destructor calls for a struct constructed for a with(), but we also want possibly immutable data in the struct, so we *have* to use this() rather than static opCall() which is the standard workaround.
>
> So we end up with code like this:
>
> struct StructType {
>   this(int) { ... }
> }
>
> StructType structType() { return StructType(0); }
>
> I do not understand what purpose the lack of this() serves. I mean, I understand what purpose it serves, it's so T.init is always equivalent to T(), but I don't understand what purpose *that* serves. T.init is *already* not a valid constructed T if you want to use any invariants and/or call any methods at all. We *already* can't treat them equivalently regardless, so from my perspective the ban on T() serves no purpose.

It's been discussed before and it has been rejected, because some motivation based on compiler internals rather than the convenience of the programmer. The whole point of a computer language is that it is supposed to convenient for humans and not the other way around.

Also, I don't see the point why we couldn't hack in support for a constructor without any arguments. We already have support for a constructor with arguments so I don't understand why without any would be impossible to implement.

It's is highly inconvenient and instead I have to use an additional init() method with no argument but when I have arguments I can use the constructor directly. This really makes the language inconsistent.

November 06, 2020
On Friday, 6 November 2020 at 09:54:23 UTC, FeepingCreature wrote:
> I do not understand what purpose the lack of this() serves. I mean, I understand what purpose it serves, it's so T.init is always equivalent to T(), but I don't understand what purpose *that* serves. T.init is *already* not a valid constructed T if you want to use any invariants and/or call any methods at all. We *already* can't treat them equivalently regardless, so from my perspective the ban on T() serves no purpose.

I understand the reasons. Type.init is nice to have for all types, but, unfortunately, life isn't fair and Type.init is garbage for some types, not only those with invariants. One could argue---and I'm leaning towards that side at least somewhat---that an invalid Type.init is worse than Type.init not existing for all types. If a generic algorithm won't work without Type.init then it won't work with all types. Generic algorithms not working with all types, but only those that provide specific things, is nothing new.

In my opinion, if for a struct T, T() isn't @disabled and defined, if it is pure, the compiler can reasonably say:

    enum init = T();

If T's this() isn't pure, T.init cannot exist. Use cases where this() wouldn't be pure are probably rare anyway. As a first step, the compiler could disallow impure this() at all. That being said, debug{} could be used to do something at construction in an impure manner.
November 06, 2020
On Friday, 6 November 2020 at 16:53:32 UTC, Q. Schroll wrote:
>
> If T's this() isn't pure, T.init cannot exist.

Why must T.init and this() be the same thing?

November 06, 2020
On Friday, 6 November 2020 at 17:14:27 UTC, IGotD- wrote:
> On Friday, 6 November 2020 at 16:53:32 UTC, Q. Schroll wrote:
>>
>> If T's this() isn't pure, T.init cannot exist.
>
> Why must T.init and this() be the same thing?

There is no point in having init if you have default constructors, then init would just be an optimization.
November 07, 2020
On Friday, 6 November 2020 at 17:14:27 UTC, IGotD- wrote:
> On Friday, 6 November 2020 at 16:53:32 UTC, Q. Schroll wrote:
>>
>> If T's this() isn't pure, T.init cannot exist.
>
> Why must T.init and this() be the same thing?

The more I think about T.init the more stupid I tend to find it. The best example is

    struct S
    {
        Object obj = new Object();
    }

not working as most people expect. There's *one* Object allocated, that S.init.obj refers to, and that one Object's reference is blitted into every constructed S. One way to get different immutable(S) with their own distinct obj is a pure S factory returning unique S so that it can be implicitly typed immutable; that factory can be static opCall. This is stupid; one shouldn't need factories for that. (Factories have their use cases, though.) The only other option is an immutable constructor (or a constructor suitable for constructing immutable), but since being a constructor, it needs a parameter. This is stupid; one shouldn't need to add useless parameters to any function, except maybe for compatibility with an interface. This isn't near that.

They need not necessarily. I find, it would add unnecessary confusion if T.init isn't derived from a this() call. That way, at least it is guaranteed that T.init has T's invariants met at least locally. (Locally meaning that looking at the object isolated, no invariant violation can be derived.) An example for a "global" invariant is "no two objects of that type have the same id", something that clearly cannot be verified nor refuted looking at objects individually.
November 07, 2020
On Friday, 6 November 2020 at 16:53:32 UTC, Q. Schroll wrote:
> On Friday, 6 November 2020 at 09:54:23 UTC, FeepingCreature wrote:
>> I do not understand what purpose the lack of this() serves. I mean, I understand what purpose it serves, it's so T.init is always equivalent to T(), but I don't understand what purpose *that* serves. T.init is *already* not a valid constructed T if you want to use any invariants and/or call any methods at all. We *already* can't treat them equivalently regardless, so from my perspective the ban on T() serves no purpose.
>
> I understand the reasons. Type.init is nice to have for all types, but, unfortunately, life isn't fair and Type.init is garbage for some types, not only those with invariants. One could argue---and I'm leaning towards that side at least somewhat---that an invalid Type.init is worse than Type.init not existing for all types. If a generic algorithm won't work without Type.init then it won't work with all types. Generic algorithms not working with all types, but only those that provide specific things, is nothing new.

I used to think that T.init was required for really basic stuff like Nullable and sumtype, but that kind of data type *already* doesn't work with T.init because if it's unset, it needs to bypass the broken T.init destructor call. So we need to use the union hack there anyways, as I had to do for Nullable, and do a semi-complete endrun around the compiler's lifetime tracking regardless. That's why I think the importance of T.init is overstated nowadays, and adding T() probably wouldn't break anything fundamental.
November 07, 2020
On Saturday, 7 November 2020 at 01:39:30 UTC, Q. Schroll wrote:
>
> This is stupid; one shouldn't need to add useless parameters to any function, except maybe for compatibility with an interface. This isn't near that.
>

Speaking of stupid, what the compiler can do is lowering the this() to something else.

struct T
{
    this();
}

is lowered to

struct T
{
    this(StupidCompilerMadeUpType);
}

Then it just works, basically using your workaround inside the compiler. Horrible, but it would work.
November 07, 2020
On Saturday, 7 November 2020 at 10:56:29 UTC, IGotD- wrote:
> On Saturday, 7 November 2020 at 01:39:30 UTC, Q. Schroll wrote:
>>
>> This is stupid; one shouldn't need to add useless parameters to any function, except maybe for compatibility with an interface. This isn't near that.
>>
>
> Speaking of stupid, what the compiler can do is lowering the this() to something else.
>

It's not like it doesn't work because the compiler devs couldn't figure out how to do it, it's that it clashes with the notion that all data types should have a valid default value. (As I understand it.) My point with the Nullable thing is that this notion is dead regardless.
« First   ‹ Prev
1 2