digitalmars.dip.ideas - Explicit this for methods
- vit (93/93) Jul 23 Explicit `this` allow manipulating of `this` type without
- Quirin Schroll (30/112) Jul 23 Reminds me of C++23’s explicit object parameters. In C++, using D
- vit (98/107) Jul 23 This dip idea allow this:
- vit (6/19) Jul 23 I was wrong, this:
- Quirin Schroll (169/208) Jul 25 Something like that would easily be possible if `const` could be
- Richard (Rikki) Andrew Cattermole (12/12) Jul 23 For type state analysis I do have a need to introduce ``this`` and
- Paul Backus (15/26) Jul 26 To be more precise, any attribute or storage class that is
- Quirin Schroll (18/46) Jul 27 But `ref` means that it must me an lvalue.
Explicit `this` allow manipulating of `this` type without interacting with function attributes and without need to create template function or string mixin-s. Syntax: - explicit `this` must be first parameter of non-static method - name is not identifier but keyword `this`. - explicit `this` doesn't support all parameter attributes like `ref`, `out`, `in` and `lazy`, but attributes like `scope` and `return` can be supported. Why?: - Type qualifiers can be in position of function attributes when `this` type qualifier need to be specified. Conditional manipulation of function attributes at compile time is very hard but manipulating types is much easer. - `this template parameter` can by replaced with explicit `this` + normal template parameter. - easer distinction between qualifiers/attributes for `this` and other function attributes like `pure` `nothrow` ` nogc` `ref` examples: ```d class C{ //this 3 methods are same: abstract void test1(); //method with implicit this abstract void test1(this); //method with explicit this with qualifier (mutable) abstract void test1(C this); //method with explicit this with type (is(Unqual!T == C)) //this 3 methods are same: abstract void test2()const pure; //method with implicit const this abstract void test2(const this)pure; //method with explicit this with qualifier abstract void test2(const C this)pure; //method with explicit this with type (is(Unqual!T == C)) //this 3 methods are same: void test3(this This)()const pure{} //template method with implicit this with this template parameter void test3(this This)(const this)pure{} //template method with this template parameter and explicit this with qualifier void test3(This)(const This this)pure{} //template method with normal template parameter and explicit this with type (is(Unqual!T : C)) const{ //can not be conditionaly specified at compile time abstract void foo(); abstract void foo(int i); abstract void foo(string str); } alias FooThis = const(C); //can be conditionaly specified at compile time abstract void foo2(FooThis this); abstract void foo2(FooThis this, int i); abstract void foo2(FooThis this, string str); } struct S{ //this 2 methods are same: ref S test2() safe scope return pure{return this;} ref S test2(scope return this) safe {return this;} //ref S test2(ref return this) safe {return this;} } ``` One problem is postblit constructor: ```d class C{ //this 3 ctors are same: this(); //constructor with implicit this this(this); //AMBIGUOUS: conflict with postblit this(C this); //constructor with explicit this with type (is(Unqual!T == C)) //this 3 ctors are same: this()const; //constructor with implicit const this this(const this); //AMBIGUOUS: conflict with postblit this(const C this); //constructor with explicit this with type (is(Unqual!T == C)) } ``` Explicit `this` is optional, non-static method without explicit `this` has still implicit `this`. Explicit `this` can have other parameter attributes like: - `scope` - `return scope` - `scope return` In the future explicit `this` can allow making lvalue `this`: ```d struct S{ void test1(ref this)pure; //callable only on lvalue? } ``` What you think about this idea?
Jul 23
On Tuesday, 23 July 2024 at 11:07:54 UTC, vit wrote:Explicit `this` allow manipulating of `this` type without interacting with function attributes and without need to create template function or string mixin-s.Reminds me of C++23’s explicit object parameters. In C++, using D lingo, `this` was added as a parameter storage class. The disadvantage is that in C++, the instance pointer (normally `this`) must have a name different from `this`.Syntax: - explicit `this` must be first parameter of non-static method - name is not identifier but keyword `this`. - explicit `this` doesn't support all parameter attributes like `ref`, `out`, `in` and `lazy`, but attributes like `scope` and `return` can be supported. Why?: - Type qualifiers can be in position of function attributes when `this` type qualifier need to be specified. Conditional manipulation of function attributes at compile time is very hard but manipulating types is much easier. - `this template parameter` can by replaced with explicit `this` + normal template parameter. - easier distinction between qualifiers/attributes for `this` and other function attributes like `pure` `nothrow` ` nogc` `ref` examples: ```d class C{ […] abstract void test2(const C this)pure; //method with explicit this with type (is(Unqual!T == C)) ```What is `T`? Did you intend `Unqual!(typeof(this))`?```d //this 3 methods are same: void test3(this This)()const pure{} //template method with implicit this with this template parameter void test3(this This)(const this)pure{} //template method with ```The semantics of the one above is unclear.```d […] const{ //can not be conditionaly specified at compile time abstract void foo(); abstract void foo(int i); abstract void foo(string str); } ```“can not be conditionaly specified at compile time” No idea what that means.```d alias FooThis = const(C); //can be conditionaly specified at compile time abstract void foo2(FooThis this); abstract void foo2(FooThis this, int i); abstract void foo2(FooThis this, string str); } struct S{ //this 2 methods are same: ref S test2() safe scope return pure{return this;} ref S test2(scope return this) safe {return this;} //ref S test2(ref return this) safe {return this;} } ``` One problem is postblit constructor: ```d class C{ //this 3 ctors are same: this(); //constructor with implicit this this(this); //AMBIGUOUS: conflict with postblit this(C this); //constructor with explicit this with type (is(Unqual!T == C)) //this 3 ctors are same: this()const; //constructor with implicit const this this(const this); //AMBIGUOUS: conflict with postblit this(const C this); //constructor with explicit this with type (is(Unqual!T == C)) } ```Aren’t postblits deprecated? If not, the conflict with postblits can be rectified with `this(const typeof(this) this)`.[…] Explicit `this` can have other parameter attributes like: - `scope` - `return scope` - `scope return`Logical.In the future explicit `this` can allow making lvalue `this`: ```d struct S{ void test1(ref this)pure; //callable only on lvalue? } ```There’s a bigger issue with structs. For classes, binding `this` is by value, i.e. the class reference is copied. For structs, `this` is a reference to the object. However, unlike with `ref`, of course this by-reference binding can bind rvalues. I’d do a complete 180° here: Allow `ref this` for structs and allow it to bind rvalues. A non-`ref` `this` parameter binds by copy. (For classes, no big deal, for structs, big difference.) If we had ` rvalue ref` and ` universal ref`, I’d say use those and `ref` means lvalues only. It may be a stupid question, but why allow the type of the `this` parameter to be specified? It only makes sense for templates. There, the semantics could just say that `this` simply has the type of the object it’s called on, i.e. explicit `this` implies template `this`. Another question: Can `this` parameters be used to bind by pointer? That could actually be useful for handling `null`. I.e. if I have `S* ptr` for some struct `S`, a `void f(S* this)` could bind to `ptr.f()` and handle the case that `ptr` is `null`.
Jul 23
On Tuesday, 23 July 2024 at 11:42:03 UTC, Quirin Schroll wrote:...This dip idea allow this: ```d template C(bool const_) { class C{ abstract void test1(This this){/*body 1*/} abstract void test2(This this, int i){/*body 2*/} abstract void test3(This this, string str){/*body 3*/} } static if(const_) alias This = const typeof(this); else alias This = typeof(this); } ``` instead of: ```d template C(bool const_){ class C{ static if(const_){ abstract void test1()const{return test1_impl(this);} abstract void test2(int i)const{test2_impl(this, i);} abstract void test3(string str)const{test3_impl(this, str);} } else{ abstract void test1(){return test1_impl(this);} abstract void test2(int i){test2_impl(this, i);} abstract void test3(string str){test3_impl(this, str);} } } static if(const_) alias This = const C; else alias This = C; void test1_impl(This self){/*body 1*/} void test2_impl(This self, int i){/*body 2*/} void test3_impl(This self, string str){/*body 3*/} } ``` Other example are copy constructors for ptr wrapper (like rc ptr, shared ptr, ...): ```d template Ptr(T){ private void* ptr; static foreach(alias Rhs; AliasSeq!(Ptr, const Ptr, immutable Ptr)) static foreach(alias Lhs; AliasSeq!(Ptr, const Ptr, immutable Ptr)) { static if(is(CopyTypeQualifiers!(Rhs, T*) : CopyTypeQualifiers!(Lhs, T*))) this(Lhs this, ref Rhs rhs) trusted{ this.ptr = cast(typeof(this.ptr))rhs.ptr; } else disable this(Lhs this, ref Rhs rhs) trusted; } } ``` instead of: ```d struct Ptr(T){ private void* ptr; static foreach(alias Rhs; AliasSeq!(Ptr, const Ptr, immutable Ptr)) { static if(is(CopyTypeQualifiers!(Rhs, T*) : T*)) this(ref Rhs rhs) trusted{ this.ptr = cast(typeof(this.ptr))rhs.ptr; } else disable this(ref Rhs rhs) trusted; static if(is(CopyTypeQualifiers!(Rhs, T*) : const(T*))) this(ref Rhs rhs) trusted const{ this.ptr = cast(typeof(this.ptr))rhs.ptr; } else disable this(ref Rhs rhs) trusted const; static if(is(CopyTypeQualifiers!(Rhs, T*) : immutable(T*))) this(ref Rhs rhs) trusted immutable{ this.ptr = cast(typeof(this.ptr))rhs.ptr; } else disable this(ref Rhs rhs) trusted immutable; } } ``` It simplify code and make forwarding parameters unnecessary.Yes but `typeof(this)` in this context in unclear too. :) (it is type of aggregate type in witch is method, not type of `this` parameter)```d class C{ […] abstract void test2(const C this)pure; //method with explicit this with type (is(Unqual!T == C)) ```What is `T`? Did you intend `Unqual!(typeof(this))`?
Jul 23
On Tuesday, 23 July 2024 at 14:00:30 UTC, vit wrote:On Tuesday, 23 July 2024 at 11:42:03 UTC, Quirin Schroll wrote:I was wrong, this: ```d is(Unqual!(typeof(this)) == typeof(aggregate type in which is method located)) ```...Yes but `typeof(this)` in this context in unclear too. :) (it is type of aggregate type in witch is method, not type of `this` parameter)```d class C{ […] abstract void test2(const C this)pure; //method with explicit this with type (is(Unqual!T == C)) ```What is `T`? Did you intend `Unqual!(typeof(this))`?
Jul 23
On Tuesday, 23 July 2024 at 14:00:30 UTC, vit wrote:On Tuesday, 23 July 2024 at 11:42:03 UTC, Quirin Schroll wrote:Something like that would easily be possible if `const` could be generated by a mixin. Even without: ```d class C(bool const_) { import std.conv : text; private enum q = const_ ? "const" : ""; mixin(iq{ abstract void test1() $(q); abstract void test2(int i) $(q); abstract void test3(string str) $(q); }.text); } ``` ```d class C(bool const_) { import std.format : format; pragma(msg, q{ abstract void test1() %1$s; abstract void test2(int i) %1$s; abstract void test3(string str) %1$s; }.format(const_ ? "const" : "")); } ```...This dip idea allow this: ```d template C(bool const_) { class C{ abstract void test1(This this){/*body 1*/} abstract void test2(This this, int i){/*body 2*/} abstract void test3(This this, string str){/*body 3*/} } static if(const_) alias This = const typeof(this); else alias This = typeof(this); } ```[…] Other example are copy constructors for ptr wrapper (like rc ptr, shared ptr, ...): ```d template Ptr(T){ private void* ptr; static foreach(alias Rhs; AliasSeq!(Ptr, const Ptr, immutable Ptr)) static foreach(alias Lhs; AliasSeq!(Ptr, const Ptr, immutable Ptr)) { static if(is(CopyTypeQualifiers!(Rhs, T*) : CopyTypeQualifiers!(Lhs, T*))) this(Lhs this, ref Rhs rhs) trusted{ this.ptr = cast(typeof(this.ptr))rhs.ptr; } else disable this(Lhs this, ref Rhs rhs) trusted; } } ``````d struct Ptr(T) { import std.conv : text; private void* ptr; static foreach(int i, LQ; ["", "const", "immutable"]) static foreach(int j, RQ; ["", "const", "immutable"]) { static if(is(mixin(RQ, " T")* : mixin(LQ, " T")*)) { mixin(iq{ this(ref $(RQ) Ptr rhs) $(LQ) trusted { this.ptr = cast(typeof(this.ptr))rhs.ptr; } }.text); } else { mixin(iq{ this(ref Ptr!($(RQ) T) rhs) $(LQ) trusted disable; }.text); } } } ``` It might even be clearer to manually consider every qualifier for `T` and then write specific constructors. Using `inout`, it’s possible to greatly simplify these, e.g. for any `T`, `this(inout Ptr) const` works. In particular, your and my implementation can’t handle initializing a `Ptr!(const int)` by a `Ptr!int`. If you provide examples, make them as real-world as possible, or say they are intentionally simplified or contrived. --- Again, you’re not using the best arguments for explicit `this`. Simplifying implementations can be an argument, but you have to provide evidence for ***significant*** simplification opportunities. Consider the rationales presented in [P0847](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html), the proposal to add explicit object parameters to C++23. If you write this DIP, you’d have to elaborate on it in the *Prior Work* section anyway. The paper’s arguments that also apply to D: - [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) is easy mode now - pass by value made possible - recursive lambdas are possible The CRTP is a lot weaker as an argument for D because D has no struct inheritance and class inheritance is always virtual, i.e. D has no option for static polymorphism. The best approximation is mixin templates. Taken from [Wikipedia](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Deducing_this): ```cpp // C++ code struct chef_base { template <typename Self> void signature_dish(this Self&& self) { self.cook_signature_dish(); } }; struct cafe_chef : chef_base { void cook_signature_dish() {} }; ``` The D way is: ```d // D code mixin template ChefBase() { void signatureDish() { this.cookSignatureDish(); } }; struct CafeChef { mixin ChefBase!(); void cookSignatureDish() {} }; ``` D-specific ones: - lvalue can be forced - lvalue and rvalue can be distinguished Code quadruplication is much less of an issue in D because D has `inout`, `static foreach` and string mixins. Recursive lambdas in D are a half-issue in D: ```d // okay: alias factorial = function int (int i) => i <= 1 ? 1 : factorial(i - 1) * i; // error: auto factorial = function int (int i) => i <= 1 ? 1 : factorial(i - 1) * i; ``` And if you don’t assign the lambda to anything directly (pass it as a template/function argument), you’re out of luck in D. Lambdas are quite fundamentally different in C++ and D. Also, you run into syntax issues if you insist on `this` being the “identifier” because a lambda can capture its context’s `this`. My bet is this is one reason why in C++, `this` is used as a storage class and requires an identifier. We could make an exception for lambdas – after all, lambdas’ parameter lists are special anyways: ```d void higherOrderFunction(int function(int) fp); higherOrderFunction((this factorial, int i) => i <= 1 ? 1 : factorial(i - 1) * i); ``` The factorial lambda is a 1-ary function, not a 2-ary function. A recursive `this` parameter is just a way to express the function to itself. Thinking about it, this could actually be generalized: `this` as a storage class adds a fake parameter that refers to the function itself. For a normal function, that could be allowed, but useless (and annoying, because they require you to spell their type): ```d void f(this void function() fp) { fp(); f(); } // both calls do the same struct S { void g(this void delegate() dg) { dg(); g(); } // both calls do the same } ``` But it really only makes sense for lambdas. About requiring lvalues, the situation is different in D than for C++. In C++03, if a member function exposes a reference to a data member, it’s easy to hold on to a dangling reference if the member function is called on an rvalue. So, distinguishing lvalue and rvalue objects, added in C++11, can make sense, where the rvalue overload returns the data member by move reference or moved value. (Then, calling the member function on rvalues is (quite) safe, on lvalues it’s still unsafe, but generally, you’ll get it right.) None of this is necessary in D because with DIP1000, ` safe` does some tracking and recognizes when a field reference outlive the object. There might still be use-cases where a member function isn’t meaningful for lvalues/rvalues or should behave slightly differently for lvalues and rvalues. As for any function, rvalue-only ones can be done by providing both lvalue and rvalue overloads and ` disable`-ing the lvalue one.
Jul 25
For type state analysis I do have a need to introduce ``this`` and ``return`` as function parameters. However they would not be mandatory. I can however see ``this`` having a variable name specified that overrides the implicit ``this`` pointer. If you want to force it to be mandatory you could implement a check in DScanner, however I do not think D itself would have it. I would be against such a requirement. I suspect that there might be some overlap here that would be beneficial to the language. I do want to emphasize we must not introduce this to for similarity to other languages, it must be introduced to solve problems that D has.
Jul 23
On Tuesday, 23 July 2024 at 11:07:54 UTC, vit wrote:Explicit `this` allow manipulating of `this` type without interacting with function attributes and without need to create template function or string mixin-s. Syntax: - explicit `this` must be first parameter of non-static method - name is not identifier but keyword `this`.Agree with all of this.- explicit `this` doesn't support all parameter attributes like `ref`, `out`, `in` and `lazy`, but attributes like `scope` and `return` can be supported.To be more precise, any attribute or storage class that is currently [specified as applying to the `this` parameter when used on a method][1] should be supported. Personally, I would also **require** the `this` parameter of a `struct` method to be declared as `ref`, since [`this` is always passed by reference][2]. (An explicit `ref` is not needed for `class` methods, because classes are reference types.) [1]: https://dlang.org/spec/class.html#member-functions [2]: https://dlang.org/spec/expression.html#this- `this template parameter` can by replaced with explicit `this` + normal template parameter.I'm not sure allowing the user to specify the type of the `this` parameter is a great idea. Everything else in this proposal is just a new syntax for existing behavior; this would add entirely new behavior to the language.
Jul 26
On Saturday, 27 July 2024 at 00:08:19 UTC, Paul Backus wrote:On Tuesday, 23 July 2024 at 11:07:54 UTC, vit wrote:But `ref` means that it must me an lvalue. A decision must be made if this proposal wants an alternative syntax for non-static member functions or if it wants to add something to the language that isn't expressible in the current state. My argument is that adding explicit `this` has a low chance of adoption if it adds nothing that can't already be expressed. It's best rationale, … I have it spelled out in quite some detail in my previous post – not gonna repeat that. If we were discussing *how* to add non-static member functions, that proposal would be a good contender against implicit this, but we already have implicit this, so if this isn't going to add something, it has little chance of getting through. It's not good for a language if there are two competing ways of doing things which are roughly equal and low in complexity. That can lead to style wars. It would be a completely different story if this were to propose deprecating implicit this.Explicit `this` allow manipulating of `this` type without interacting with function attributes and without need to create template function or string mixin-s. Syntax: - explicit `this` must be first parameter of non-static method - name is not identifier but keyword `this`.Agree with all of this.- explicit `this` doesn't support all parameter attributes like `ref`, `out`, `in` and `lazy`, but attributes like `scope` and `return` can be supported.To be more precise, any attribute or storage class that is currently [specified as applying to the `this` parameter when used on a method][1] should be supported. Personally, I would also **require** the `this` parameter of a `struct` method to be declared as `ref`, since [`this` is always passed by reference][2]. (An explicit `ref` is not needed for `class` methods, because classes are reference types.) [1]: https://dlang.org/spec/class.html#member-functions [2]: https://dlang.org/spec/expression.html#this- `this template parameter` can by replaced with explicit `this` + normal template parameter.I'm not sure allowing the user to specify the type of the `this` parameter is a great idea. Everything else in this proposal is just a new syntax for existing behavior; this would add entirely new behavior to the language.
Jul 27