August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/1/17 7:44 PM, H. S. Teoh via Digitalmars-d-learn wrote:
> On Tue, Aug 01, 2017 at 07:31:41PM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote:
>> On 8/1/17 7:15 PM, H. S. Teoh via Digitalmars-d-learn wrote:
>>> On Tue, Aug 01, 2017 at 07:09:45PM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote:
>>>> If this were a true implementation without the opaqueness, it
>>>> would not work properly.
>>> [...]
>>>
>>> Actually, a proper implementation would still work, provided you
>>> declare your pointer types carefully. Sketch of idea:
>>>
>>> auto byKeyValue(AA)(AA aa) {
>>> struct Result {
>>> const(Slot)* current; // N.B.: proper type
>>> bool empty() { ... }
>>> auto front() { return Pair(*current); }
>>> void popFront() {
>>> current = current.next;
>>> ...
>>> }
>>> }
>>> return Result(aa);
>>> }
>>>
>>> Basically, the type of `current` must be const(Slot)* rather than
>>> const(Slot*), which would be the default inferred type. But since
>>> it's legal to assign a const pointer to a pointer to const (you
>>> can't modify the original pointer, nor what it points to, but it's
>>> valid to copy the pointer to a mutable pointer variable, as long as
>>> what is pointed to is still const), this actually will work without
>>> breaking / bypassing the type system.
>>
>> No, you can't const the Slot, because if the value type is a pointer,
>> you can't then cast away the const-ness of it legally.
>>
>> There are ways to get it right, it involves templating for mutability.
> [...]
>
> Counter-proof:
>
> struct Slot {
> Slot* next;
> const(string) key;
> const(int) value;
> }
> struct AA {
> Slot*[] slots;
> }
> unittest {
> const(AA) aa;
>
> static assert(is(typeof(aa.slots[0]) == const(Slot*)));
> const(Slot)* p = aa.slots[0]; // N.B.: legal
> p = p.next; // N.B.: legal
> }
>
> Note especially the type checked for in the static assert: you cannot
> modify any of the Slot pointers in the AA, but you *can* assign them to
> mutable (but tail-const) pointers. Therefore you totally can iterate a
> const AA without any casts or any breaking of the type system. And
> there's no need to template for mutability either.
You can iterate a const AA, but if you want to iterate a non-const AA, you need a different type.
For instance, if your AA is int*[string], you will get const(int*) out of it, which may not be what you want.
Unless your range is a slice or a pointer, then you can't do it properly without having separate types for const, immutable, mutable ranges. You can use templates to build them, but it's not straightforward or pleasant.
-Steve
|
August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Wed, Aug 02, 2017 at 08:20:23AM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote: > On 8/1/17 7:44 PM, H. S. Teoh via Digitalmars-d-learn wrote: [...] > You can iterate a const AA, but if you want to iterate a non-const AA, you need a different type. > > For instance, if your AA is int*[string], you will get const(int*) out of it, which may not be what you want. > > Unless your range is a slice or a pointer, then you can't do it properly without having separate types for const, immutable, mutable ranges. You can use templates to build them, but it's not straightforward or pleasant. [...] Hmm. This seems like a perfect use case for inout, though I'm not confident the current implementation of inout can handle this. You'd do something like this: auto byPair(AA)(inout(AA) aa) { struct Result { inout(Slot)* current; ... // range primitives here } return Result(aa); } What I'm unsure of is whether the current implementation of inout can handle the inout inside the definition of Result. T -- Trying to define yourself is like trying to bite your own teeth. -- Alan Watts |
August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/2/17 11:52 AM, H. S. Teoh via Digitalmars-d-learn wrote:
> On Wed, Aug 02, 2017 at 08:20:23AM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote:
>> On 8/1/17 7:44 PM, H. S. Teoh via Digitalmars-d-learn wrote:
> [...]
>> You can iterate a const AA, but if you want to iterate a non-const AA,
>> you need a different type.
>>
>> For instance, if your AA is int*[string], you will get const(int*) out
>> of it, which may not be what you want.
>>
>> Unless your range is a slice or a pointer, then you can't do it
>> properly without having separate types for const, immutable, mutable
>> ranges. You can use templates to build them, but it's not
>> straightforward or pleasant.
> [...]
>
> Hmm. This seems like a perfect use case for inout, though I'm not
> confident the current implementation of inout can handle this. You'd do
> something like this:
>
> auto byPair(AA)(inout(AA) aa)
> {
> struct Result
> {
> inout(Slot)* current;
> ... // range primitives here
> }
> return Result(aa);
> }
>
> What I'm unsure of is whether the current implementation of inout can
> handle the inout inside the definition of Result.
It's not currently legal, you can't have inout members of a struct. This could be added, but it still wouldn't work, because you can't "strip off" the inout part upon return.
The real answer is to have tail modifiers for structs, so you can do the same thing an array does. Note that if Result is an array, you CAN use inout:
auto byPair(AA)(inout(AA) aa)
{
alias Result = inout(X)[];
return Result(...);
}
-Steve
|
August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Wed, Aug 02, 2017 at 01:15:44PM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote: [...] > It's not currently legal, you can't have inout members of a struct. This could be added, but it still wouldn't work, because you can't "strip off" the inout part upon return. > > The real answer is to have tail modifiers for structs, so you can do the same thing an array does. Note that if Result is an array, you CAN use inout: > > auto byPair(AA)(inout(AA) aa) > { > alias Result = inout(X)[]; > return Result(...); > } [...] Yeah, this isn't the first time I've run into this. But then the problem becomes, how do you design tail modifiers for structs? Because here the inout (or its equivalent) has to apply to one specific member; the range methods can't also inherit the modifier otherwise in the const case you wouldn't be able to implement popFront(). I suppose, within the current type system, you'd have to template on modifiers, as you said, so that the incoming modifier is properly represented in the resulting type. But since we're already templating on the AA type, perhaps what we could do is something like: auto byPair(AA)(inout(AA) aa) { alias Modifiers = std.traits.getModifiers!AA; struct Result { std.traits.ApplyModifiers!(Slot*, Modifiers) slot; ... // range methods here } return Result(aa); } Of course, getModifiers and ApplyModifiers are fictitious Phobos templates, but you get the idea. T -- Дерево держится корнями, а человек - друзьями. |
August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
On Wed, Aug 02, 2017 at 11:06:03AM -0700, H. S. Teoh via Digitalmars-d-learn wrote: [...] > auto byPair(AA)(inout(AA) aa) > { > alias Modifiers = std.traits.getModifiers!AA; > struct Result { > std.traits.ApplyModifiers!(Slot*, Modifiers) slot; > ... // range methods here > } > return Result(aa); > } > > Of course, getModifiers and ApplyModifiers are fictitious Phobos templates, but you get the idea. [...] Hmm, actually, they don't have to be fictitious; here's an actual, compilable example: struct Slot { Slot* next; string key; int value; } struct AA { Slot*[] slots; } auto byPair(AA)(AA aa) { import std.traits : QualifierOf; alias Qual = QualifierOf!AA; struct Result { Qual!(Slot)* slot; bool empty() { return slot is null; } auto front() { struct Front { Qual!string key; Qual!int value; } return Front(slot.key, slot.value); } void popFront() { slot = slot.next; } } return Result(aa.slots[0]); } unittest { AA aa; const(AA) constAa; immutable(AA) immAa; auto mutPair = aa.byPair; static assert(is(typeof(mutPair.front.value) == int)); auto constPair = constAa.byPair; static assert(is(typeof(constPair.front.value) == const(int))); auto immPair = immAa.byPair; static assert(is(typeof(immPair.front.value) == immutable(int))); } T -- If Java had true garbage collection, most programs would delete themselves upon execution. -- Robert Sewell |
August 02, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 8/2/17 2:06 PM, H. S. Teoh via Digitalmars-d-learn wrote: > On Wed, Aug 02, 2017 at 01:15:44PM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote: > [...] >> It's not currently legal, you can't have inout members of a struct. >> This could be added, but it still wouldn't work, because you can't >> "strip off" the inout part upon return. >> >> The real answer is to have tail modifiers for structs, so you can do >> the same thing an array does. Note that if Result is an array, you CAN >> use inout: >> >> auto byPair(AA)(inout(AA) aa) >> { >> alias Result = inout(X)[]; >> return Result(...); >> } > [...] > > Yeah, this isn't the first time I've run into this. But then the > problem becomes, how do you design tail modifiers for structs? I have ideas :) I just haven't fleshed them out enough to present the case. I've told Andrei about them and his reaction was not positive. But I think eventually D will need them. > Because > here the inout (or its equivalent) has to apply to one specific member; > the range methods can't also inherit the modifier otherwise in the const > case you wouldn't be able to implement popFront(). > > I suppose, within the current type system, you'd have to template on > modifiers, as you said, so that the incoming modifier is properly > represented in the resulting type. But since we're already templating > on the AA type, perhaps what we could do is something like: > > auto byPair(AA)(inout(AA) aa) > { > alias Modifiers = std.traits.getModifiers!AA; > struct Result { > std.traits.ApplyModifiers!(Slot*, Modifiers) slot; > ... // range methods here > } > return Result(aa); > } > > Of course, getModifiers and ApplyModifiers are fictitious Phobos > templates, but you get the idea. Yes, but of course inout doesn't play a role here, as it's not a compile-time construct. Just: auto byPair(AA)(AA aa) -Steve |
August 03, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On Wednesday, 2 August 2017 at 18:06:03 UTC, H. S. Teoh wrote:
> On Wed, Aug 02, 2017 at 01:15:44PM -0400, Steven Schveighoffer via Digitalmars-d-learn wrote: [...]
>> The real answer is to have tail modifiers for structs, so you can do the same thing an array does. Note that if Result is an array, you CAN use inout:
>>
>> auto byPair(AA)(inout(AA) aa)
>> {
>> alias Result = inout(X)[];
>> return Result(...);
>> }
> [...]
>
> Yeah, this isn't the first time I've run into this. But then the problem becomes, how do you design tail modifiers for structs?
I understand the general concept you're describing, but what exactly are tail modifiers? It's the first time I see this name, and my google-fu gives me nothing.
|
August 03, 2017 Re: why won't byPair work with a const AA? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Olivier FAURE | On 8/3/17 4:30 AM, Olivier FAURE wrote:
> I understand the general concept you're describing, but what exactly are tail modifiers? It's the first time I see this name, and my google-fu gives me nothing.
tail modifiers are modifiers that only apply to the "tail" of the type.
For example const(int)* is applying const to the "tail" of the int pointer, that is, the int that it points at. It's not applying to the actual pointer itself (we call that the "head").
Another way to look at it is that when you copy a variable you always make a copy of the head, and you don't make a copy of the tail. For this reason, the fully-modified and tail-modified types are implicitly castable, and this is the important property we need to duplicate.
A tail-modified struct would be something like this:
struct S
{
int * x;
}
const(S) s;
This looks like this:
struct ConstS
{
const(int *) x;
}
A tail-const S would look like this:
struct TailConstS
{
const(int)* x;
}
That is, everything the struct points at remains const, but the actual data of the struct is now mutable.
Note that TailConstS and ConstS can implicitly convert.
This is how arrays work. An array T[] is really:
struct Array
{
size_t length;
T* ptr;
}
A tail-const array const(T)[] is really:
struct TailConstArray
{
size_t length; // mutable
const(T)* ptr; // mutable, but points at const data
}
This property of implicit casting is what's needed to fully realize custom ranges and const together. The way to get it is to define tail-modifier syntax for all types, not just arrays and pointers.
-Steve
|
Copyright © 1999-2021 by the D Language Foundation