difficulties with const structs and alias this / template functions

Steven Schveighoffer schveiguy at gmail.com
Mon Nov 19 14:51:14 UTC 2018


On 11/18/18 1:17 PM, Stanislav Blinov wrote:
> On Sunday, 18 November 2018 at 17:30:18 UTC, Dennis wrote:
>> I'm making a fixed point numeric type and want it to work correctly 
>> with const. First problem:
>>
>> ```
>> const q16 a = 6;
>> a /= 2;          // compiles! despite `a` being const.
> 
> Ouch. That's actually kind of nasty.
> 
>> writeln(a);      // still 6
>> a.toQ32 /= 2;    // what's actually happening
>> ```
>>
>> My q16 type has an implicit conversion to q32 (like how int can be 
>> converted to long):
>> ```
>> q32 toQ32() const {
>>   return q32(...);
>> }
>> alias toQ32 this;
>> ```
>> How do I make it so that a const(q16) will be converted to a 
>> const(q32) instead of mutable q32?
> 
> Like this:
> 
>          // implement separate methods for mutable/const/immutable
>          q32 toQ32() {
>              return q32(x);
>          }
> 
>          const(q32) toQ32() const {
>              return q32(x);
>          }
> 
>          immutable(q32) toQ32() immutable {
>              return q32(x);
>          }
> 
> Or like this:
> 
>          // implement all three in one method, using the `this template` 
> feature
>          auto toQ32(this T)() {
>              static if (is(T == immutable))
>                  return immutable(q32)(x);
>              else static if (is(T == const))
>                  return const(q32)(x);
>              else
>                  return q32(x);
>          }

Or just use inout. This is literally what inout is for:

inout(q32) toQ32 inout {
     return q32(x);
}

This should transfer whatever constancy of the original is used for the 
return value.

However, I'd state that this is really a workaround for a language 
deficiency. In reality, I would surmise (without knowing the 
implementation) that q32's state is a complete copy of the q16 state. So 
there is no reason to apply any constancy copying from the source to the 
destination.

The real problem is that mutating operators on struct rvalues are always 
allowed, because `this` is always passed by reference. For the most part 
this is a harmless drawback, but because there is no way to "opt out" of 
this, you can't stop it when it really doesn't make sense (as in this case).

>> Second problem:
>> ```
>> Q log2(Q)(Q num) if (is(Q : q16) || is(Q : q32)) {
>>     import std.traits: Unqual;
>>     Unqual!Q x = num;
>>     // actual code
>> }
>> ```
>> When I call this with a const(q16), Q is resolved to const(q16) so I 
>> have to unqualify Q every time. It works, but feels clumsy. Is there 
>> an easier way to automatically de-const parameters? We're working with 
>> small value types here, it should be simple.
> 
> Define different overloads for Q and const Q. Or this:
> 
> Q log2(Q)(inout Q num) if (is(Q : q16) || is(Q : q32)) { /* ... */ }
> 
> Being able to jam mutable/const/immutable implementation in one function 
> like that should tell you that you shouldn't mutate the argument. Then, 
> the necessity to Unqual will go away on it's own ;)

I have long wanted a way to direct IFTI how to define its parameters 
base on the arguments. We have a simple adjustment for arrays, where the 
array is always unqual'd before IFTI define the parameter.

In other words:

const int[] arr;

void foo(T)(T t) {... }

foo(arr) => T = const(int)[], not const(int[])

I think Andrei in the last dconf proposed we do more of this with other 
types (for ranges, specifically). But I think it would be good to also 
define other conversions possibly manually.

-Steve


More information about the Digitalmars-d-learn mailing list