Tail Const (Was: I closed a very old bug!)
Simen Kjærås
simen.kjaras at gmail.com
Fri Jan 19 22:34:00 UTC 2018
On Thursday, 18 January 2018 at 09:15:04 UTC, Simen Kjærås wrote:
> At any rate, this is a topic for a DIP.
So, a few more thoughts on this:
Arrays and pointers automatically decay to their Unqual'ed
variants when passed to a templated function. AAs do not, which
makes sense given their reference-type nature. Structs and
classes don't, and in general shouldn't. Simple types, like ints
and floats, don't.
As a matter of fact, Unqual is too blunt an instrument for what
we want to do - Unqual!(const(MyStruct)) == MyStruct, regardless
of what's inside MyStruct. For a struct with aliasing (one that
contains pointers or arrays), that behavior is plain wrong for
our use case.
What is actually needed here is a template that gives the
equivalent head-mutable type, such that it can be put in a
variable. For const(int), that's int. For const(int[]), int[].
For a const(MyStruct), it's const(MyStruct) or MyStruct,
depending on whether MyStruct has aliasing. This test already
exists in the compiler (try to assign a const struct {int[] arr;
int i;} to a mutable variable, then try the same without the
array).
This takes care of the non-templated problems, but the big issue
is still ahead of us. For templates, as stated earlier, the
connection between T and its head-mutable variant can be
arbitrarily complex. However, a single function template is all
that's needed to convey all the necessary information. Let's call
it opDecay, and give this implementation of the basic logic:
template Decay(T)
{
import std.traits : Unqual, hasAliasing, isAssociativeArray;
static if (is(typeof(T.init.opDecay())))
{
alias Decay = typeof(T.init.opDecay());
}
else static if (is(T == class) || isAssociativeArray!T ||
(is(T == struct) && hasAliasing!(Unqual!T)))
{
alias Decay = T;
}
else
{
alias Decay = Unqual!T;
}
}
unittest
{
// Regular types:
assert(is(Decay!(const int) == int));
assert(is(Decay!(const int*) == const(int)*));
assert(is(Decay!(const int[]) == const(int)[]));
assert(is(Decay!(const int[10]) == int[10]));
assert(is(Decay!(const int[int]) == const(int[int])));
// Struct without aliasing:
static struct S1 {
int n;
immutable(int)[] arr;
}
assert(is(Decay!(const S1) == S1));
// Struct with aliasing:
static struct S2 {
int[] arr;
}
assert(is(Decay!(const S2) == const S2));
// Struct with aliasing, with opDecay hook:
static struct S3 {
int[] arr;
S3 opDecay(this T)() {
return S3(arr.dup);
}
}
assert(is(Decay!(const S3) == S3));
// Templated struct with aliasing, with opDecay hook
struct S4(T) {
T[] arr;
auto opDecay(this This)() const {
import std.traits : CopyTypeQualifiers;
return S4!(CopyTypeQualifiers!(This, T))(arr.dup);
}
}
assert(is(Decay!(const S4!int) == S4!(const int)));
}
We now have a way of obtaining head-mutable variables of any type
that supports it. Alias this takes care of implicit conversion to
the decayed type ...except when it doesn't. As stated above,
arrays decay when passed to a templated function:
import std.range;
void foo(T)(T arr) {
assert(is(T == const(int)[]));
assert(isInputRange!T);
}
unittest {
const(int[]) a;
foo(a);
assert(!isInputRange!(typeof(a)));
}
This behavior is special - no types other than T* and T[] decay
in this way, and there's no way to tell the compiler you want
your type to do the same. Is it important that our types do the
same? I'm not entirely sure. The fact that this decay is
inconsistent[1] today makes me even less sure. If we want the
same kind of behavior for user-defined types, the compiler will
need to insert calls to opDecay when a type with that method is
passed to a function[2].
opDecay is likely to be a small function that can be inlined and
in many cases elided altogether, and will only be used for a
small subset of types, so I believe the overhead of calling it
whenever a type with the method is passed to another function,
would be negligible.
Destroy!
--
Simen
[1]: https://issues.dlang.org/show_bug.cgi?id=18268
[2]: If typeof(x.opDecay) != typeof(x), and the type of the
parameter is not explicitly typeof(x).
More information about the Digitalmars-d
mailing list