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