Jump to page: 1 2
Thread overview
const, in and final
May 27, 2004
Arcane Jill
May 27, 2004
Hauke Duden
May 27, 2004
Arcane Jill
May 27, 2004
Hauke Duden
Re: the linker (was: const, in and final)
May 28, 2004
Kevin Bealer
May 28, 2004
Juan C
May 28, 2004
Kevin Bealer
May 29, 2004
Arcane Jill
May 30, 2004
Kevin Bealer
May 27, 2004
Kris
May 27, 2004
Antti Sykäri
May 27, 2004
Kris
May 27, 2004
Arcane Jill
May 27, 2004
Kris
May 27, 2004
Regan Heath
May 27, 2004
Antti Sykäri
May 27, 2004
Kris
May 28, 2004
Derek Parnell
Jun 04, 2004
Matthew
May 27, 2004
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
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
>> 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
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
"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
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
"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
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
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
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