View mode: basic / threaded / horizontal-split · Log in · Help
May 27, 2004
const, in and final
I have given a lot of thought to the whole "const" thing, and it seems to me
that there is a way around this that will keep everybody happy. (It was not I
who thought of this, by the way - I'm just clarifying).

Summary - the story so far:
Three uses of const have been identified in C++
(1) To declare a complile-time constant
(2) To declare an instance of something which needs to be stored in ROM
(3) As part of Design-By-Contract, to specify a contract that a function will
not modify something

The keyword "const" is implemented in D to allow use (1) only.

It has been suggested that the distinction between (1) and (2) could be decided
by the compiler. Alas, that may not be so. Given the declaration:

>       const int FORTY_TWO = 42;

the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
(2). Therefore, if we wish the language to support option (2), we need a need a
new keyword. I suggest re-using "final". Thus:

>       const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot take address
>       final int FORTY_TWO = 42;       // Stored in ROM, so address can be taken, but value may not be changed

This brings us to the can of worms that is definition (3). I believe we can do
this. I believe it can work. Here's how:

The CURRENT situation is that function parameters may be declared in three
different ways. These are:

(i)   in (the default)
(ii)  out
(iii) inout

To implement DbC-readonly assertions, we actually need FOUR states. For
consistency, these should be:

(i)   (default)
(ii)  in
(iii) out
(iv)  inout

(default) is what you get if you specify none of the other keywords. For
DbC-readonly to work, we only need to state that out, inout, and default, shall
work exactly as before (current behavior is unchanged), but new behavior needs
to be defined for anything declared using "in". I shall describe this new
behavior below.

Note though that in addition to its placement before formal function paramters,
"in" must also be placeable before member function declarations, like this:

>       class A
>       {
>           in void f()         // a contract that the function will not modify any of A's member variables
>           {
>               // do stuff
>           }
>       }

For reference, the C++ syntax for doing this is:

>       class A
>       {
>           void f() const      // a contract that the function will not modify any of A's member variables
>           {
>               // do stuff
>           }
>       };

Above, I talked about a difference in behavior. The important question is HOW
this is going to work? Specifically, what gets passed where? How can the
compiler spot a contract violation? These questions I shall answer, now.

To the first point, parameters are passed exactly as they are now. Current
behavior is suffient.

To the second point, given the declaration:

>       void f(in SomeObject a)

the following would all be compile-time errors if they occur within the body of
the function:

(1)     a = expression;     // illegal always
(2)     a.m = expression;   // illegal always
(3)     a.g();              // illegal unless g() was declared as in void g()
(4)     g(a)                // illegal unless g() was declared as void g(in
SomeObject a)

These tests are sufficient to catch all contract violations at compile-time.
(The same tests could also be used to verify that "final" objects are not
modified).

Incidently, the statement:

>       this = expression;

should /always/ be illegal, regardless of whether or not the function was
declared with "in". Note that currently, that statement is allowed, The
consequences of executing it are undefined.

CONSEQUENCES

If we were starting from scratch, there would be no consequences. But we are not
starting from scratch. We already have a large body of code out there, much of
which will not immediately compile under this proposed change.

There is an immediate consequence for getter functions (properties). All
property getter functions would have to be redclared, like this:

>       int x() { return x_; }      // BEFORE
>       in int x() { return x_; }   // AFTER

Similarly, there is an immediate consequence for the non-assigning operator
overloads, which would also have to be redeclared:

>       SomeObject opAdd(SomeObject x)          // BEFORE
>       in SomeObject opAdd(in SomeObject x)    // AFTER

Then, similarly, you redeclare every other function that requires it, in every
file, least-dependent (that is, most low-level) file first. The absolute
simplest way to do this is to declare EVERYTHING that isn't "out" or "inout" as
"in", and then remove the "in" keyword from things that won't compile. Providing
you attack the source code in the right order, this should convert everything
that needs it. And it might even show up a few bugs that got missed along the
way!

CONCLUSION

This is achieveable, and simple. The remaining questions are: (i) does the D
community want this, given that recoding of a lot of source files will be
necessary, and (ii) how does Walter feel about implementing it, given that the
illegalities would be relatively easy to spot?

OH YEAH - I FORGOT

Given the declaration:

>       f(out SomeType x)

I would *assume* (though I haven't done the experiment) that either the caller
or the callee overwrites any former value of x with the init value of SomeType,
before doing anything with x. If it doesn't, it should.


Arcane Jill
May 27, 2004
Re: const, in and final
Arcane Jill wrote:
> Summary - the story so far:
> Three uses of const have been identified in C++
> (1) To declare a complile-time constant
> (2) To declare an instance of something which needs to be stored in ROM
> (3) As part of Design-By-Contract, to specify a contract that a function will
> not modify something
> 
> The keyword "const" is implemented in D to allow use (1) only.
> 
> It has been suggested that the distinction between (1) and (2) could be decided
> by the compiler. Alas, that may not be so. Given the declaration:
> 
> 
>>      const int FORTY_TWO = 42;
> 
> 
> the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
> (2). Therefore, if we wish the language to support option (2), we need a need a
> new keyword. 

Why?

const int FORTY_TWO=42;

means that ist an integer with the value 42 which will never change. 
Thus &FORTY_TWO is perfectly legal.

The distrinction you're talking about is an optimization. When seeing 
FORTY_TWO being used as a value then the compiler knows that it can 
safely insert the constant 42 instead. And if the address of FORTY_TWO 
is NEVER needed then it may even decide to axe the integer object 
completely.

But, this is all transparent to the programmer and completely done 
behind the scenes by the compiler. So I fail to see the problem.

Hauke
May 27, 2004
Re: const, in and final
>> the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
>> (2). Therefore, if we wish the language to support option (2), we need a need a
>> new keyword. 
>
>Why?
>
>const int FORTY_TWO=42;
>
>means that ist an integer with the value 42 which will never change. 
>Thus &FORTY_TWO is perfectly legal.
>
>The distrinction you're talking about is an optimization. When seeing 
>FORTY_TWO being used as a value then the compiler knows that it can 
>safely insert the constant 42 instead. And if the address of FORTY_TWO 
>is NEVER needed then it may even decide to axe the integer object 
>completely.
>
>But, this is all transparent to the programmer and completely done 
>behind the scenes by the compiler. So I fail to see the problem.
>
>Hauke


The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, but the
expression "&FORTY_TWO" occurs in module B. When compiling module A, how is the
compiler to know (without clairvoyance) what module B is going to do? It can't
optimize FORTY_TWO away if there's even a /chance/ that some other module might
take its address. Yet ... we want it to make that optimization. Don't we?

Jill
May 27, 2004
Re: const, in and final
Arcane Jill wrote:
>>>the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
>>>(2). Therefore, if we wish the language to support option (2), we need a need a
>>>new keyword. 
>>
>>Why?
>>
>>const int FORTY_TWO=42;
>>
>>means that ist an integer with the value 42 which will never change. 
>>Thus &FORTY_TWO is perfectly legal.
>>
>>The distrinction you're talking about is an optimization. When seeing 
>>FORTY_TWO being used as a value then the compiler knows that it can 
>>safely insert the constant 42 instead. And if the address of FORTY_TWO 
>>is NEVER needed then it may even decide to axe the integer object 
>>completely.
>>
>>But, this is all transparent to the programmer and completely done 
>>behind the scenes by the compiler. So I fail to see the problem.
>>
>>Hauke
> 
> 
> 
> The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, but the
> expression "&FORTY_TWO" occurs in module B. When compiling module A, how is the
> compiler to know (without clairvoyance) what module B is going to do? It can't
> optimize FORTY_TWO away if there's even a /chance/ that some other module might
> take its address. Yet ... we want it to make that optimization. Don't we?

I always thought this kind of optimization (throwing away unused 
entities) is done by the linker. Isn't it?

Anyway, I don't think that having some additional integers floating 
around would be any problem. The additional overhead certainly does not 
justify introducing two different kinds of const keywords. And if it is 
a problem for a specialized application you could still let the compiler 
compile all modules at once.

For me this smells like the "register" keyword all over again.

Hauke
May 27, 2004
Re: const, in and final
"Arcane Jill" wrote
> Summary - the story so far:
> Three uses of const have been identified in C++
> (1) To declare a complile-time constant
> (2) To declare an instance of something which needs to be stored in ROM
> (3) As part of Design-By-Contract, to specify a contract that a function
will
> not modify something

> The keyword "const" is implemented in D to allow use (1) only.

Possibly misleading AJ. From the documentation:

----------------------------
Const means constant
Const is not a type modifier in D, it is a storage class. Hence, the value
of a const cannot change. A const declaration can be put in read-only
storage, and the optimizer can assume its value never changes. This is
unlike C/C++, where since const is a type modifier, the value of a reference
to a const can legally change.
----------------------------

This would appear to include (2) as well.


> const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot
take address

I fail to understand why const is used like this when enum is (apparently)
designed explicitly for this purpose. I think this type of const usage
should be tossed, along with the Octal prefix everyone loves so much <g>
May 27, 2004
Re: const, in and final
In article <c95ab4$19a1$1@digitaldaemon.com>, Kris wrote:
> 
> "Arcane Jill" wrote
> Possibly misleading AJ. From the documentation:
> 
> ----------------------------
> Const means constant
> Const is not a type modifier in D, it is a storage class. Hence, the value
> of a const cannot change. A const declaration can be put in read-only
> storage, and the optimizer can assume its value never changes. This is
> unlike C/C++, where since const is a type modifier, the value of a reference
> to a const can legally change.
> ----------------------------

(Um, and I just have to say that the documentation itself seems to be
hinting that values of constant declarations cannot be put into
read-only memory in C++, which they can. Const _references_ are a
different business.)

> This would appear to include (2) as well.
> 
> 
>> const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot
> 
> I fail to understand why const is used like this when enum is (apparently)
> designed explicitly for this purpose. I think this type of const usage
> should be tossed, along with the Octal prefix everyone loves so much <g>

Because enums cannot be used to declare constant floats, character
literals, other objects or (indeed) have their address taken.

Enums, I think, are designed for the purpose of - well - enumerating. :)

-Antti


-- 
I will not be using Plan 9 in the creation of weapons of mass destruction
to be used by nations other than the US.
May 27, 2004
Re: const, in and final
"Antti Sykäri" wrote
> >> const int FORTY_TWO = 42;       // Not stored anywhere, therefore
cannot
> >
> > I fail to understand why const is used like this when enum is
(apparently)
> > designed explicitly for this purpose. I think this type of const usage
> > should be tossed, along with the Octal prefix everyone loves so much <g>
>
> Because enums cannot be used to declare constant floats, character
> literals, other objects or (indeed) have their address taken.
>
> Enums, I think, are designed for the purpose of - well - enumerating. :)
>
> -Antti

Right :-)  I guess I wasn't very clear on that one.

That particular example from AJ was *only* for those constants that cannot
have an address taken. The "read-only" style of const (2) would hande
literals, objects, structs etc. If enum cannot handle floats then perhaps it
could? It is legal to set a D enum type, but I guess that covers int and
char only? Perhaps I'm being naiive ...

- Kris
May 27, 2004
Re: const, in and final
In article <c95pbn$1vov$1@digitaldaemon.com>, Kris says...
>

>That particular example from AJ was *only* for those constants that cannot
>have an address taken.

I did give an example, but it was *only* an example. The underlying point was
generic. It wasn't practical to give the whole infinity of possible examples. In
general, I tend to give the simplest example I can think of.

Perhaps a better example would have been:

>       const int[10000] = 
>           // loads of data

In any case, it was not I who claimed that this was a problem, I merely repeated
it. Another poster (forgive me, I can't remember who) was interested in ROM
storage for firmware and stuff.

To be honest, this isn't a big deal to me either. By the DbC problem is.

Jill
May 27, 2004
Re: const, in and final
I think the default behaviour, i.e. that of 'in' *should* be changed.

I think it should:
1- pass by reference (even for structs)
2- enforce what you have mentioned below, compile time checking that the 
variable is not changed.

The reasoning for 1 is that typically you do not want to copy-in a struct, 
doing so for a large struct is in-efficient, and if you want to modify it 
then either:
a- it is incorrectly defined as an 'in' parameter
b- you only want to do so for the functions scope so why not:

struct foo {
  int a;
  int b;
  int c;
}

int foobar(in foo _abc) {
  foo abc = _abc;
}

The reasoning for 2 is an obvious dbc rule, if a variable is not an out or 
inout then it should not get modified by the function.


I also think if you replace the word 'in' below with 'const' it looks like 
what you have said *is* exactly what const is in C/C++..


I agree there needs to be some way of saying that a method of a class does 
not modify it's members, so it can then be used on/with an 'in' parameter, 
why not:

class A {
  void foo(in this, int a) {  //does not modify members of A
  }
  void foo(int a) {  //does modify members of A (or rather does not 
guarantee not to)
  }
}

in other words an *optional* definition for the this parameter.


I realise my proposed change to the behaviour of 'in' will possibly break 
existing code which is expecting the copy-in behaviour of structs.. but I 
believe it is the *right* way to do it, and as D is not set in stone yet..


On Thu, 27 May 2004 08:55:21 +0000 (UTC), Arcane Jill 
<Arcane_member@pathlink.com> wrote:
> I have given a lot of thought to the whole "const" thing, and it seems 
> to me
> that there is a way around this that will keep everybody happy. (It was 
> not I
> who thought of this, by the way - I'm just clarifying).
>
> Summary - the story so far:
> Three uses of const have been identified in C++
> (1) To declare a complile-time constant
> (2) To declare an instance of something which needs to be stored in ROM
> (3) As part of Design-By-Contract, to specify a contract that a function 
> will
> not modify something
>
> The keyword "const" is implemented in D to allow use (1) only.
>
> It has been suggested that the distinction between (1) and (2) could be 
> decided
> by the compiler. Alas, that may not be so. Given the declaration:
>
>>       const int FORTY_TWO = 42;
>
> the expression (&FORTY_TWO) should be illegal in case (1), but legal in 
> case
> (2). Therefore, if we wish the language to support option (2), we need a 
> need a
> new keyword. I suggest re-using "final". Thus:
>
>>       const int FORTY_TWO = 42;       // Not stored anywhere, therefore 
>> cannot take address
>>       final int FORTY_TWO = 42;       // Stored in ROM, so address can 
>> be taken, but value may not be changed
>
> This brings us to the can of worms that is definition (3). I believe we 
> can do
> this. I believe it can work. Here's how:
>
> The CURRENT situation is that function parameters may be declared in 
> three
> different ways. These are:
>
> (i)   in (the default)
> (ii)  out
> (iii) inout
>
> To implement DbC-readonly assertions, we actually need FOUR states. For
> consistency, these should be:
>
> (i)   (default)
> (ii)  in
> (iii) out
> (iv)  inout
>
> (default) is what you get if you specify none of the other keywords. For
> DbC-readonly to work, we only need to state that out, inout, and 
> default, shall
> work exactly as before (current behavior is unchanged), but new behavior 
> needs
> to be defined for anything declared using "in". I shall describe this new
> behavior below.
>
> Note though that in addition to its placement before formal function 
> paramters,
> "in" must also be placeable before member function declarations, like 
> this:
>
>>       class A
>>       {
>>           in void f()         // a contract that the function will not 
>> modify any of A's member variables
>>           {
>>               // do stuff
>>           }
>>       }
>
> For reference, the C++ syntax for doing this is:
>
>>       class A
>>       {
>>           void f() const      // a contract that the function will not 
>> modify any of A's member variables
>>           {
>>               // do stuff
>>           }
>>       };
>
> Above, I talked about a difference in behavior. The important question 
> is HOW
> this is going to work? Specifically, what gets passed where? How can the
> compiler spot a contract violation? These questions I shall answer, now.
>
> To the first point, parameters are passed exactly as they are now. 
> Current
> behavior is suffient.
>
> To the second point, given the declaration:
>
>>       void f(in SomeObject a)
>
> the following would all be compile-time errors if they occur within the 
> body of
> the function:
>
> (1)     a = expression;     // illegal always
> (2)     a.m = expression;   // illegal always
> (3)     a.g();              // illegal unless g() was declared as in 
> void g()
> (4)     g(a)                // illegal unless g() was declared as void 
> g(in
> SomeObject a)
>
> These tests are sufficient to catch all contract violations at 
> compile-time.
> (The same tests could also be used to verify that "final" objects are not
> modified).
>
> Incidently, the statement:
>
>>       this = expression;
>
> should /always/ be illegal, regardless of whether or not the function was
> declared with "in". Note that currently, that statement is allowed, The
> consequences of executing it are undefined.
>
> CONSEQUENCES
>
> If we were starting from scratch, there would be no consequences. But we 
> are not
> starting from scratch. We already have a large body of code out there, 
> much of
> which will not immediately compile under this proposed change.
>
> There is an immediate consequence for getter functions (properties). All
> property getter functions would have to be redclared, like this:
>
>>       int x() { return x_; }      // BEFORE
>>       in int x() { return x_; }   // AFTER
>
> Similarly, there is an immediate consequence for the non-assigning 
> operator
> overloads, which would also have to be redeclared:
>
>>       SomeObject opAdd(SomeObject x)          // BEFORE
>>       in SomeObject opAdd(in SomeObject x)    // AFTER
>
> Then, similarly, you redeclare every other function that requires it, in 
> every
> file, least-dependent (that is, most low-level) file first. The absolute
> simplest way to do this is to declare EVERYTHING that isn't "out" or 
> "inout" as
> "in", and then remove the "in" keyword from things that won't compile. 
> Providing
> you attack the source code in the right order, this should convert 
> everything
> that needs it. And it might even show up a few bugs that got missed 
> along the
> way!
>
> CONCLUSION
>
> This is achieveable, and simple. The remaining questions are: (i) does 
> the D
> community want this, given that recoding of a lot of source files will be
> necessary, and (ii) how does Walter feel about implementing it, given 
> that the
> illegalities would be relatively easy to spot?
>
> OH YEAH - I FORGOT
>
> Given the declaration:
>
>>       f(out SomeType x)
>
> I would *assume* (though I haven't done the experiment) that either the 
> caller
> or the callee overwrites any former value of x with the init value of 
> SomeType,
> before doing anything with x. If it doesn't, it should.
>
>
> Arcane Jill
>
>
>



-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
May 27, 2004
Re: const, in and final
I was the poster vis-a-vis RO segments, but I thought your original post
made the correct distinction AJ: here it is again

Summary - the story so far:
Three uses of const have been identified in C++
(1) To declare a complile-time constant
(2) To declare an instance of something which needs to be stored in ROM
(3) As part of Design-By-Contract, to specify a contract that a function
will
not modify something

I thought you were suggesting (1) should be things you cannot take the
address of? If so, I'd fully agree. I tend to equate (1) with enum, since
they are compile-time constants.

(2) doesn't *need* to be stored in ROM, but it declares the instance as RO
and is therefore a ROM candidate. This, I thought, would have included your
{const int[10000] = ...} as noted below, for which you should be able to
take an address of. However, your post below is in the context of
non-addressable items like (1) ... Did you mean {final int [10000] = }
instead, per your suggested syntax change, or are you saying that (1) should
be addressable?

Confused again;

- Kris


"Arcane Jill" <Arcane_member@pathlink.com> wrote in message
news:c95qlo$21ru$1@digitaldaemon.com...
> In article <c95pbn$1vov$1@digitaldaemon.com>, Kris says...
> >
>
> >That particular example from AJ was *only* for those constants that
cannot
> >have an address taken.
>
> I did give an example, but it was *only* an example. The underlying point
was
> generic. It wasn't practical to give the whole infinity of possible
examples. In
> general, I tend to give the simplest example I can think of.
>
> Perhaps a better example would have been:
>
> >       const int[10000] =
> >           // loads of data
>
> In any case, it was not I who claimed that this was a problem, I merely
repeated
> it. Another poster (forgive me, I can't remember who) was interested in
ROM
> storage for firmware and stuff.
>
> To be honest, this isn't a big deal to me either. By the DbC problem is.
>
> Jill
>
>
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home