The Status of Const
Steven Schveighoffer
schveiguy at yahoo.com
Mon Aug 16 08:39:15 PDT 2010
On Mon, 16 Aug 2010 10:50:04 -0400, Michel Fortin
<michel.fortin at michelf.com> wrote:
> On 2010-08-16 10:03:58 -0400, "Steven Schveighoffer"
> <schveiguy at yahoo.com> said:
>
>> On Mon, 16 Aug 2010 09:26:55 -0400, Michel Fortin
>> <michel.fortin at michelf.com> wrote:
>>
>>> I think what you want for that is to somehow make SmartPtr!(X)
>>> implicitly derived from SmartPtr!(const X), you don't want the
>>> compiler applying blindly tail-const to all the members.
>> Again, it's logical const, no matter how you slice it.
>
> If you see it that way, then D const is logical const too.
Logical const fits within the realm of const. I've proven in the past
that logical const can be emulated without modification to the type
system. But applying tail const is not applying logical const. If you
want to apply a different type of const to a type, then that is not const
or tail-const. It's not invalid or unsafe, but it's another type of const
besides tail const and normal const.
> I'm not proposing we create a mutable island inside a const struct or
> class (thus not breaking the transitivity), so it does abide by D
> current definition of const. Here's some definition?
>
> struct SmartPointer(T) {
> T* pointer;
> uint* refCount;
> }
>
> SmartPointer!(const X) pointer;
> All I am saying is that the "conceptual" tail-const form of
> SmartPointer!(X) is SmartPointer!(const X), and that it'd be useful that
> SmartPointer!(X) and SmartPointer!(immutable X) could implicitly cast to
> SmartPointer!(const X).
Likewise, you could say the const version of SmartPointer only applies
const to the pointer part, because refcount is not part of its state. It
was the basis for my argument for including a way to do logical const in
the past. It's logical const, or rather, logical tail-const :)
> That might be "logical const" to you as long as you think "tail const"
> for struct is "all members become tail const", but I think that
> definition of tail-const is pointless and that there should not be any
> real tail-const for structs except the one you can induce through its
> template arguments.
Is implicitly converting from const(int[]) to const(int)[] useless? If
you think that, then I agree that we disagree.
When making a tail-const copy of a const struct, you should be able to
simply remove the const decorations of the values you are copying, but not
the data it references. That violates the guarantee of const.
Translating it to your example, should you be able to convert from a
const(SmartPointer!X) to a SmartPointer!(const X) ? I'd say no, because
const(SmartPointer!X) guarantees that the refCount will not change.
Implicit casting to your version of tail-const breaks that guarantee. One
of the *essential* properties of tail const is you should always be able
to implicitly convert a const(T) to a tail const(T).
> As for constness of ranges, it's quite similar. The thing is that you
> generally have three possible levels of constness. The range might be
> const, the container the range points to might be const, and the
> elements in the container might be const. If ranges were expressed like
> this:
>
> struct Range(ContainerType) {...}
>
> then it'd be easy to say whether the container and the element type is
> const or not just like this:
>
> Range!(Container!(const Element))
> Range!(const Container!(immutable Element))
> Range!(immutable Container!(immutable Element))
A range on a container is typically already parameterized on the
container, because it's usually a subtype of the container. But I think
that in order for container ranges to do the right thing, containers of
const elements should return tail const ranges of non-const elements.
Otherwise, implicit conversion would be impossible.
So given a container parameterized with Element, and a function:
R opSlice() const {...}
The R type should be @tail const(Container!Element.range), even on a
Container!(const(Element)).
> Most ranges are not defined like this: the type of the containeris
> implied in the type of the range, but the constness of the container is
> simply missing.
Yes, that is one of the reasons I have avoided doing const ranges on
dcollections, all ranges use the container's parameters for the element
type.
> Wouldn't defining ranges like the above fix the problem? And you could
> make ranges cast implicitly to their "tail-const" form when needed,
> exactly like the smart pointer above.
It might make it possible, but it would be a mess. A range would be a
templated type inside the templated container type. Or you could define a
range outside the container type to avoid odd issues. But implicit
casting is going to be difficult. You also have to contend with stuff
like this:
struct Range(V)
{
static if(isConst!(V)) // not sure if this exists, but assume it does
{
bool makeUncastable;
}
V *cur;
}
So a Range!(const(V)) cannot be implicitly converted to Range!V because
the layout is different. I'd rather not rely on templates and
compile-time decisions, and just let the compiler enforce the simple cases.
Note that it has been proposed in the past to have constancy a template
parameter in itself, but this has a host of problems as well.
-Steve
More information about the Digitalmars-d
mailing list