www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Implicit conversion to mutable if no indirections?

reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
An issue I have is non-mutable expressions without indirections not 
converting to mutable. I talked about this before and mentioned Unqual 
at DConf 2022 to explain how it solves this.

The problem happens in template code:

void main() {
   const i = 42;  // const, so foo's T will be const below
   foo(i);
}

void foo(T)(T value) {
   T result;
   ++result;  // Compilation error; but should it work?
}

I feel like it should work because 'result' is a local int.

But I am aware that we can't deduce T to be 'int' because we would be 
losing that qualifier and further template deductions would be wrong. :/

Anyway... That's one thing that bugs me occasionally and causes bugs 
like the following one, which I think is caused by a missing Unqual:

   https://issues.dlang.org/show_bug.cgi?id=23319

At least, there is a moral here at least for myself: Don't forget to 
test your templates with const, etc.

Ali
Sep 02 2022
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
```d
void main() {
	auto foo = const Foo("whatever");
	bar(foo);
}

struct Foo {
	char[] data;
}

void bar(T)(T value) {
	T thing = value;
	thing.data[0] = 'a';
}
```

Do you want this to error at compile time or at runtime due to writing 
to read only memory?
Sep 02 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 9/2/22 12:05, rikki cattermole wrote:

 struct Foo {
      char[] data;
 }
 Do you want this to error at compile time or at runtime due to writing
 to read only memory?
Fair enough but that type has an indirection. I "feel" it should work for fundamental types, etc. without indirections but as I said, I understand it will ruin template type deduction as well as function overloading which I forgot to mention. This thread is here to remind us to test our templates for const, etc. :) Ali
Sep 02 2022
parent rikki cattermole <rikki cattermole.co.nz> writes:
On 03/09/2022 7:19 AM, Ali Çehreli wrote:
 Fair enough but that type has an indirection. I "feel" it should work 
 for fundamental types, etc. without indirections but as I said, I 
 understand it will ruin template type deduction as well as function 
 overloading which I forgot to mention.
Yeah that's the problem isn't it, its exceptions in the type system that you have to add to get it to work for basic types only. For other types like slices, pointers, structs and classes it gets dangerous.
Sep 02 2022
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/2/22 2:58 PM, Ali Çehreli wrote:
 An issue I have is non-mutable expressions without indirections not 
 converting to mutable. I talked about this before and mentioned Unqual 
 at DConf 2022 to explain how it solves this.
 
 The problem happens in template code:
 
 void main() {
    const i = 42;  // const, so foo's T will be const below
    foo(i);
 }
 
 void foo(T)(T value) {
    T result;
    ++result;  // Compilation error; but should it work?
 }
 
 I feel like it should work because 'result' is a local int.
result is a local `const int`. That's because `T` is a `const int`. IFTI uses the exact type passed, not a different type.
 But I am aware that we can't deduce T to be 'int' because we would be 
 losing that qualifier and further template deductions would be wrong. :/
It's not just that, there is no syntax to say to IFTI, "if you match type T, I really want you to use type U". It would actually be nice, but I don't know how it can be done. The one exception to the "exact type" rule is for arrays and pointers, which automatically convert to their tail versions. This is a special case for the benefit of range code. Perhaps the same rules should be done for value types? -Steve
Sep 02 2022
next sibling parent Mathias LANG <pro.mathias.lang gmail.com> writes:
On Saturday, 3 September 2022 at 01:50:05 UTC, Steven 
Schveighoffer wrote:
 The one exception to the "exact type" rule is for arrays and 
 pointers, which automatically convert to their tail versions. 
 This is a special case for the benefit of range code.

 Perhaps the same rules should be done for value types?

 -Steve
I think it's also done to reduce the number of template instantiations. And I agree, it should be done for value types.
Sep 03 2022
prev sibling next sibling parent reply Daniel N <no public.email> writes:
On Saturday, 3 September 2022 at 01:50:05 UTC, Steven 
Schveighoffer wrote:

 It's not just that, there is no syntax to say to IFTI, "if you 
 match type T, I really want you to use type U". It would 
 actually be nice, but I don't know how it can be done.

 -Steve
C++ solved this... maybe we could add this feature to D? https://en.cppreference.com/w/cpp/language/class_template_argument_deduction#:~:text=x%7B2.0%2C%201%7D%3B-,User%2Ddefined%20deduction%20guides,-The%20syntax%20of
Sep 03 2022
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/3/22 7:09 AM, Daniel N wrote:
 On Saturday, 3 September 2022 at 01:50:05 UTC, Steven Schveighoffer wrote:
 
 It's not just that, there is no syntax to say to IFTI, "if you match 
 type T, I really want you to use type U". It would actually be nice, 
 but I don't know how it can be done.
C++ solved this... maybe we could add this feature to D? https://en.cppreference.com/w/cpp/language/class_template_argument_deduction#:~:text=x%7B2.0%2C%201%7D%3B-,User%2Ddefined%20deduction%20guide ,-The%20syntax%20of
Yeah, this would be useful. -Steve
Sep 03 2022
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Saturday, 3 September 2022 at 01:50:05 UTC, Steven 
Schveighoffer wrote:
 It's not just that, there is no syntax to say to IFTI, "if you 
 match type T, I really want you to use type U". It would 
 actually be nice, but I don't know how it can be done.
The following works, the pragma only prints once. ```d void f(T)(const T v) { pragma(msg, T); // int T result; ++result; } void main() { const int c; f(c); f(int()); } ```
Sep 03 2022
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/3/22 10:15 AM, Nick Treleaven wrote:
 On Saturday, 3 September 2022 at 01:50:05 UTC, Steven Schveighoffer wrote:
 It's not just that, there is no syntax to say to IFTI, "if you match 
 type T, I really want you to use type U". It would actually be nice, 
 but I don't know how it can be done.
The following works, the pragma only prints once. ```d void f(T)(const T v) {     pragma(msg, T); // int     T result;     ++result; } void main() {     const int c;     f(c);     f(int()); } ```
This solves the OP's problem, but I am certain that is just a toy example. What I want to see is the *parameter* properly typed. Either left as const if it's a reference type or stripped of const if it's a value type. -Steve
Sep 03 2022
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On Friday, 2 September 2022 at 18:58:43 UTC, Ali Çehreli wrote:
 An issue I have is non-mutable expressions without indirections 
 not converting to mutable. I talked about this before and 
 mentioned Unqual at DConf 2022 to explain how it solves this.

 The problem happens in template code:

 void main() {
   const i = 42;  // const, so foo's T will be const below
   foo(i);
 }

 void foo(T)(T value) {
   T result;
   ++result;  // Compilation error; but should it work?
 }

 I feel like it should work because 'result' is a local int.
My take on this is that passing by value is equivalent of shallow (1 level deep) unqual. This is sadly not representable in the current language. struct A { int[] slice; } void main() { const a = A([1, 2, 3]); pass(a); // still have const a here } void pass(Unqual!(const A) value) { // value is really this: // struct A { const(int)[]slice; } value.slice ~= 4; // should work } — Dmitry Olshansky
Sep 03 2022
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Friday, 2 September 2022 at 18:58:43 UTC, Ali Çehreli wrote:
 But I am aware that we can't deduce T to be 'int' because we 
 would be losing that qualifier and further template deductions 
 would be wrong. :/
Another feature that would be interesting is if an `auto` declaration stripped const/immutable where possible. After all, if the user didn't want that they could've used `const` or `immutable`. ```d const i = 4; // const int auto v = i; // int const a = [0]; // const(int[]) auto s = a; // const(int)[] ```
Sep 04 2022
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 4 September 2022 at 11:03:53 UTC, Nick Treleaven wrote:
 ```d
 const i = 4; // const int
 auto v = i;  // int
 const a = [0]; // const(int[])
 auto s = a;    // const(int)[]
 ```
P I got different results. Which compiler are you using? For example, the content of v cannot be changed: ```d void main() {  const i = 4;   // const(int)  auto v = i;    // const(int)  /* onlineapp.d(6): Error: cannot modify `const` expression `v`    --v; //*/  const a = [0]; // const(const(int)[])  auto b = a;    // const(const(int)[])  const c = [i]; // const(const(int)[])  auto d = c;   // const(const(int)[]) //typeid(d).writeln; } ``` SDB 79
Sep 04 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 9/4/22 05:24, Salih Dincer wrote:
 On Sunday, 4 September 2022 at 11:03:53 UTC, Nick Treleaven wrote:
 ```d
 const i = 4; // const int
 auto v = i;  // int
 const a = [0]; // const(int[])
 auto s = a;    // const(int)[]
 ```
P I got different results.
Nick Treleaven was showing how it could be *if* 'auto' actually worked that way. Ali
Sep 04 2022
prev sibling parent reply Mathias LANG <pro.mathias.lang gmail.com> writes:
On Sunday, 4 September 2022 at 11:03:53 UTC, Nick Treleaven wrote:
 On Friday, 2 September 2022 at 18:58:43 UTC, Ali Çehreli wrote:
 But I am aware that we can't deduce T to be 'int' because we 
 would be losing that qualifier and further template deductions 
 would be wrong. :/
Another feature that would be interesting is if an `auto` declaration stripped const/immutable where possible. After all, if the user didn't want that they could've used `const` or `immutable`. ```d const i = 4; // const int auto v = i; // int const a = [0]; // const(int[]) auto s = a; // const(int)[] ```
This has been discussed in another thread and Walter approved it. Just need to implement it.
Sep 04 2022
parent reply Meta <jared771 gmail.com> writes:
On Sunday, 4 September 2022 at 12:33:37 UTC, Mathias LANG wrote:
 On Sunday, 4 September 2022 at 11:03:53 UTC, Nick Treleaven 
 wrote:
 On Friday, 2 September 2022 at 18:58:43 UTC, Ali Çehreli wrote:
 But I am aware that we can't deduce T to be 'int' because we 
 would be losing that qualifier and further template 
 deductions would be wrong. :/
Another feature that would be interesting is if an `auto` declaration stripped const/immutable where possible. After all, if the user didn't want that they could've used `const` or `immutable`. ```d const i = 4; // const int auto v = i; // int const a = [0]; // const(int[]) auto s = a; // const(int)[] ```
This has been discussed in another thread and Walter approved it. Just need to implement it.
He did? This is straight out of Scott Myers' "The Last Thing D Needs" talk. He uses almost the same code as an example too.
Sep 04 2022
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Sunday, 4 September 2022 at 14:45:15 UTC, Meta wrote:
 On Sunday, 4 September 2022 at 12:33:37 UTC, Mathias LANG wrote:
 On Sunday, 4 September 2022 at 11:03:53 UTC, Nick Treleaven 
 wrote:
 On Friday, 2 September 2022 at 18:58:43 UTC, Ali Çehreli 
 wrote:
 But I am aware that we can't deduce T to be 'int' because we 
 would be losing that qualifier and further template 
 deductions would be wrong. :/
Another feature that would be interesting is if an `auto` declaration stripped const/immutable where possible. After all, if the user didn't want that they could've used `const` or `immutable`. ```d const i = 4; // const int auto v = i; // int const a = [0]; // const(int[]) auto s = a; // const(int)[] ```
This has been discussed in another thread and Walter approved it. Just need to implement it.
He did? This is straight out of Scott Myers' "The Last Thing D Needs" talk. He uses almost the same code as an example too.
I ran into this while writing a DIP that “abused” `const`. In C++, `const` is a far lower guarantee and it’s more like a convention than actual compiler checking. I really dislike the fact that Walter made `const(int*)` become `const(int)*` as well as `const(int[])` become `const(int)[]` when an object of the former types is passed to a function template as an argument which has its type inferred: ```D void f(T)(T x) { pragma(msg, "parameter: ", T); } void main() { const int[] xs; pragma(msg, "argument : ", typeof(xs)); f(xs); const int* p; pragma(msg, "argument : ", typeof(p)); f(p); } ``` difference between built-in stuff and user-defined stuff. (One In C++, there’s has been lot of work put in to give user-defined types the same possibilities that built-in types have. In D, there’s much more special casing around built-in types that make meta-programming tedious. Some examples: * Value-range propagation: Given `int x`, `x & 0x7FFF` converts to `short` (but `x` alone requires `cast(int)`. Nice for concrete code, but in meta-programming code, it opens the door for hard-to-understand errors appearing. * Aforementioned `const(int*)` becoming `const(int)*`. That does not happen with custom `struct S(T)` automatically even in cases where it is provably correct, and there is no way to tell the D compiler that copies of `const(S!int)` are better understood to be `S!(const int)`. * Types are keywords: With `alias` we could have `alias int = __traits(signed_integer_type, 32);` etc. There were a lot of issues in the parser that could be avoided. If you think this leads to problems, note that `string` is such an alias already. * Type constructors are hard-wired: If templates want to deconstruct a type, a lot of special cases have to be written because in `int*`, `int[]`, `int[10]`, and `int[string]`, the `*`, `[]`, `[N]`, `[T]` is not syntactic sugar for (hypothetical) `Ptr!int`, `Slice!int`, `Array!(int, 10)`, and `AssocArray!(string, int)`, respectively, so those would be matched by `void f(alias TT, T)(TT!T value)`. Technically speaking, `const`, `immutable` etc. and `function`/`delegate` are type constructors as well, but those have additional things going on: e.g. `const` methods and a function’s parameters have more on them than their type (e.g., `ref`). As in the bullet above: `alias Ptr = __traits(pointer_type_ctor);`, etc. The Last Thing D Needs is the opposite of streamlining.
Sep 06 2022
next sibling parent reply Loara <loara noreply.com> writes:
On Tuesday, 6 September 2022 at 10:06:59 UTC, Quirin Schroll 
wrote:

 * Aforementioned `const(int*)` becoming `const(int)*`. That 
 does not happen with custom `struct S(T)` automatically even in 
 cases where it is provably correct, and there is no way to tell 
 the D compiler that copies of `const(S!int)` are better 
 understood to be `S!(const int)`.
Consider ```d struct S(T){ T val; int idx; void inc(){ idx++; } } ``` then `s.inc()` is valid if `s` id `S!(const int)` but not if `s` is `const S!int`! Again consider ```d struct S(T){ int dat; T func(T dat){ ... } } ``` in this example conversion of `S!(const int)` to `const S!int` has much less sense. If you want to perform a such conversion then you should instead introduce a "cloning" method: ```d auto clone() const pure{ return S(...); } ```
 * Types are keywords: With `alias` we could have `alias int = 
 __traits(signed_integer_type, 32);` etc. There were a lot of 
 issues in the parser that could be avoided. If you think this 
 leads to problems, note that `string` is such an alias already.
 * Type constructors are hard-wired: If templates want to 
 deconstruct a type, a lot of special cases have to be written 
 because in `int*`, `int[]`, `int[10]`, and `int[string]`, the 
 `*`, `[]`, `[N]`, `[T]` is not syntactic sugar for 
 (hypothetical) `Ptr!int`, `Slice!int`, `Array!(int, 10)`, and 
 `AssocArray!(string, int)`, respectively, so those would be 
 matched by `void f(alias TT, T)(TT!T value)`.
Traits exists for this reason: provide a common interface for hard wired types and templated ones.
Sep 06 2022
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 6 September 2022 at 11:09:26 UTC, Loara wrote:
 On Tuesday, 6 September 2022 at 10:06:59 UTC, Quirin Schroll 
 wrote:

 * Aforementioned `const(int*)` becoming `const(int)*`. That 
 does not happen with custom `struct S(T)` automatically even 
 in cases where it is provably correct, and there is no way to 
 tell the D compiler that copies of `const(S!int)` are better 
 understood to be `S!(const int)`.
Consider ```d struct S(T){ T val; int idx; void inc(){ idx++; } } ``` then `s.inc()` is valid if `s` id `S!(const int)` but not if `s` is `const S!int`!
Of course. That’s e.g. what a reference counted pointer would do.
 Again consider
 ```d
 struct S(T){
    int dat;
    T func(T dat){ ... }
 }
 ```
 in this example conversion of `S!(const int)` to `const S!int` 
 has much less sense. If you want to perform a such conversion 
 then you should instead introduce a "cloning" method:
This one isn’t even vaguely a container of `T`. You could argue about `int*`, and the case for `int[]` is obvious. I’m not entirely sure what you want to tell me. In the cases of containers, it’s probably even possible to a large degree to use introspection to provide a wide-ranging `clone`. The relevant part is that when you clone, you have to be explicit. There’s tension between stuff being handy when using known types and known functions, and stuff being simple using templates’ type arguments and function templates.
Sep 06 2022
parent Loara <loara noreply.com> writes:
On Tuesday, 6 September 2022 at 17:48:53 UTC, Quirin Schroll 
wrote:
 This one isn’t even vaguely a container of `T`.
Exactly, what you say is valid only for containers not for generic templated objects. It's better to provide a custom method that apply the needed conversion: ```d struct myContainer(T){ ... const(myContainer!(Unconst!T)) shareConst() const{ //do stuff } } ```
 I’m not entirely sure what you want to tell me. In the cases of 
 containers, it’s probably even possible to a large degree to 
 use introspection to provide a wide-ranging `clone`. The 
 relevant part is that when you clone, you have to be explicit.
I don't think that could be possible to implement such feature without heavy drawbacks, it's smarter to implement custom conversion methods for each templated object since "cloning an object" may assume different meaning depending of its type. For example in [this project](https://github.com/Loara/dclone) cloning an object means "create a new object that doesn't share any indirection with the original one", however this condition is often too restrictive for containers since you don't want to clone each stored object. An unique solution that works for every object doesn't exists, it's better that each programmer implements the preferred strategy in his code.
Sep 07 2022
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 6 September 2022 at 10:06:59 UTC, Quirin Schroll 
wrote:
 I ran into this while writing a DIP that “abused” `const`. In 
 C++, `const` is a far lower guarantee and it’s more like a 
 convention than actual compiler checking. I really dislike the 
 fact that Walter made `const(int*)` become `const(int)*` as 
 well as `const(int[])` become `const(int)[]` when an object of 
 the former types is passed to a function template as an 
 argument which has its type inferred:
 ```D
 void f(T)(T x) { pragma(msg, "parameter: ", T); }
 void main()
 {
     const int[] xs;
     pragma(msg, "argument : ", typeof(xs));
     f(xs);
     const int* p;
     pragma(msg, "argument : ", typeof(p));
     f(p);
 }
 ```
Fun fact: I actually had to work around this behavior a little while ago, in order to get `SumType` working correctly with `inout`. You can see the workaround and its application in this Phobos commit: https://github.com/dlang/phobos/commit/a74fa63e6775d626850d8ebd854d9803c7ffb97d I can't find the post, but think it was Andrei who once remarked on these forums that the adjustment of `qual(T[])` to `qual(T)[]` was all upside, with essentially zero cost. Well, here's your cost. Special cases are like drugs. They can make you feel good in the short term, but sooner or later, the bill will always come due. Just Say No, kids.
Sep 06 2022
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 6 September 2022 at 10:06:59 UTC, Quirin Schroll 
wrote:
 * Aforementioned `const(int*)` becoming `const(int)*`. That 
 does not happen with custom `struct S(T)` automatically even in 
 cases where it is provably correct, and there is no way to tell 
 the D compiler that copies of `const(S!int)` are better 
 understood to be `S!(const int)`.
There is at least a library pattern: https://dlang.org/blog/2020/06/25/a-pattern-for-head-mutable-structures/
Sep 06 2022
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 September 2022 at 17:45:50 UTC, Nick Treleaven 
wrote:
 On Tuesday, 6 September 2022 at 10:06:59 UTC, Quirin Schroll 
 wrote:
 * Aforementioned `const(int*)` becoming `const(int)*`. That 
 does not happen with custom `struct S(T)` automatically even 
 in cases where it is provably correct, and there is no way to 
 tell the D compiler that copies of `const(S!int)` are better 
 understood to be `S!(const int)`.
There is at least a library pattern: https://dlang.org/blog/2020/06/25/a-pattern-for-head-mutable-structures/
I really wish that had gone somewhere. There's not a lot of code to it, it's almost all library code that the user doesn't see, and it would provide a solution while we wait for the promised compiler support for the same thing (which I don't think would be very different - you'll need to specify the behavior for complex cases anyway). Also, it's not worth a whole lot as a library if Phobos doesn't implement it. -- Simen
Sep 07 2022