www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Explicit this for methods

reply vit <vit vit.vit> writes:
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
next sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
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
parent reply vit <vit vit.vit> writes:
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.
 ```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))`?
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)
Jul 23
next sibling parent vit <vit vit.vit> writes:
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:
 ...
 ```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))`?
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)
I was wrong, this: ```d is(Unqual!(typeof(this)) == typeof(aggregate type in which is method located)) ```
Jul 23
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
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:
 ...
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); } ```
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" : "")); } ```
[…]

 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
prev sibling next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
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
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
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
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
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:
 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.
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.
Jul 27