On Mon, 16 Aug 2010 10:50:04 -0400, Michel Fortin <michel.for...@michelf.com> wrote:

On 2010-08-16 10:03:58 -0400, "Steven Schveighoffer" <schvei...@yahoo.com> said:

On Mon, 16 Aug 2010 09:26:55 -0400, Michel Fortin <michel.for...@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

Reply via email to