digitalmars.D - const, in and final
- Arcane Jill (82/106) May 27 2004 I have given a lot of thought to the whole "const" thing, and it seems t...
- Hauke Duden (13/32) May 27 2004 Why?
- Arcane Jill (6/21) May 27 2004 The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, bu...
- Hauke Duden (10/39) May 27 2004 I always thought this kind of optimization (throwing away unused
- Kevin Bealer (16/30) May 27 2004 In C++ at least, the linker throws away compilation units (files) when n...
- Juan C (6/11) May 28 2004 I think this is one of the things I mentioned when I first heard about D...
- Kevin Bealer (10/21) May 28 2004 Out of curiosity, why not? Doing this manually takes a lot of effort, a...
- Arcane Jill (9/18) May 29 2004 ..there is a problem doing this in D. The way it's designed. private stu...
- Kevin Bealer (39/59) May 30 2004 I agree, there is a problem with doing it using user-level tools before ...
- Kris (16/24) May 27 2004 Possibly misleading AJ. From the documentation:
- Antti =?iso-8859-1?Q?Syk=E4ri?= (12/32) May 27 2004 (Um, and I just have to say that the documentation itself seems to be
- Kris (10/19) May 27 2004 (apparently)
- Arcane Jill (10/14) May 27 2004 I did give an example, but it was *only* an example. The underlying poin...
- Kris (28/43) May 27 2004 I was the poster vis-a-vis RO segments, but I thought your original post
- Regan Heath (40/180) May 27 2004 I think the default behaviour, i.e. that of 'in' *should* be changed.
- Antti =?iso-8859-1?Q?Syk=E4ri?= (16/144) May 27 2004 This proposition has merit. Instead of advocating "const" to the
- Kris (48/154) May 27 2004 I'd like to attempt a further clarification regarding the three types of
- Derek Parnell (14/26) May 27 2004 So this idea is a LITERAL value that does not take up any RAM at runtime...
- Matthew (10/116) Jun 04 2004 Interesting. I look forward to hearing what Walter thinks of this, but I...
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 changedThis 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_; } // AFTERSimilarly, 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) // AFTERThen, 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: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. Haukeconst 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.
May 27 2004
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? Jillthe 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
Arcane Jill wrote: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. HaukeThe 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?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
In article <c94nff$e11$1 digitaldaemon.com>, Hauke Duden says... ..In C++ at least, the linker throws away compilation units (files) when no references to them are found. Anything smaller than that is probably not removed by the linker. I spent all day today (at work) moving stuff between compilation units to reduce executable sizes. Compiler experts: What do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively. I reduced a C++ executable by 30MB by doing this by hand, and I only removed dependency on one library, so I think it could help all-around. (Yes this was debug mode, our release mode binaries are not that large). KevinThe 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
What do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively.I think this is one of the things I mentioned when I first heard about D. I got into the habit of putting only one function in each file when I worked on a large team using C, and that can be done with C++ too (if I recall correctly). I up over several files. Tiny code files offer several benefits to teams. However, I believe the compiler/linker shouldn't do it _for_ you.
May 28 2004
In article <c97r1k$1v9u$1 digitaldaemon.com>, Juan C says...Out of curiosity, why not? Doing this manually takes a lot of effort, and has no benefit, except for the dubious C-language stoicism of having walked the whole distance on foot and not gotten eaten by a bear (yet). What I want is dependency information to be done for "program elements", be they functions, methods, static strings, or global/module scope data objects. Then only the necessary parts of the code will need to be included. The only (functional) downside I see is the inability to write functions that can only be run from GDB via "call x(4)". KevinWhat do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively.I think this is one of the things I mentioned when I first heard about D. I got into the habit of putting only one function in each file when I worked on a large team using C, and that can be done with C++ too (if I recall correctly). I up over several files. Tiny code files offer several benefits to teams. However, I believe the compiler/linker shouldn't do it _for_ you.
May 28 2004
In article <c96kgq$651$1 digitaldaemon.com>, Kevin Bealer says...Compiler experts:I don't claim to be a compiler expert, but...What do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively. I reduced a C++ executable by 30MB by doing this by hand, and I only removed dependency on one library, so I think it could help all-around. (Yes this was debug mode, our release mode binaries are not that large)...there is a problem doing this in D. The way it's designed. private stuff has to be in the same *file* as anything which references it, and there is no package level attribute. So we can't even implement the same level of granularity to which we are accustomed in C and C++. For this, and other reasons, I am now convinced there is a stong case for the "package" attribute to exist in D. Arcane Jill
May 29 2004
In article <c99ls1$1lmi$1 digitaldaemon.com>, Arcane Jill says...In article <c96kgq$651$1 digitaldaemon.com>, Kevin Bealer says...I agree, there is a problem with doing it using user-level tools before the compile, but once in the compiler, it could be done in D, C, or C++. Compilers enforce the access attributes at the source level. If it rejects the usage, it produces an error message. If it accepts the usage, then it would still be free to write multiple objects. In C or C++, this change would extend the benefit of using one-file-per-package to all programs, even those that put all code in one file. In D, it may be less beneficial because the virtual table would probably pull in all the .o files, unless the given functions were "final". If you need the benefit in D, you could use heroic measures to get it, by writing classes like this: Module A: class Foo { final uint increase(int Z) { return impl_Foo_increase(x,y,Z); } final uint decrease(int W) { return impl_Foo_decrease(x,y,W); } private: int x,y; }; Module B: uint impl_Foo_increase(inout uint x, inout uint y, inout Z) { .. some code } Module C: uint impl_Foo_decrease(inout uint x, inout uint y, inout W) { .. some code } I know, its a bit ugly. This would also be a way to write C/C++ or C/C++/D programs. (Uh oh -- what have I unleased? Please burn this email!) There are probably people doing this already of course, but without the "final"ity. KevinCompiler experts:I don't claim to be a compiler expert, but...What do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively. I reduced a C++ executable by 30MB by doing this by hand, and I only removed dependency on one library, so I think it could help all-around. (Yes this was debug mode, our release mode binaries are not that large)...there is a problem doing this in D. The way it's designed. private stuff has to be in the same *file* as anything which references it, and there is no package level attribute. So we can't even implement the same level of granularity to which we are accustomed in C and C++. For this, and other reasons, I am now convinced there is a stong case for the "package" attribute to exist in D. Arcane Jill
May 30 2004
"Arcane Jill" wroteSummary - 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 functionwillnot modify somethingThe 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 cannottake 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.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.const int FORTY_TWO = 42; // Not stored anywhere, therefore cannotI 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
"Antti Sykäri" wrotecannotconst int FORTY_TWO = 42; // Not stored anywhere, therefore(apparently)I fail to understand why const is used like this when enum isRight :-) 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 ... - Krisdesigned 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
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 dataIn 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 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...cannotThat particular example from AJ was *only* for those constants thatwashave an address taken.I did give an example, but it was *only* an example. The underlying pointgeneric. It wasn't practical to give the whole infinity of possibleexamples. Ingeneral, I tend to give the simplest example I can think of. Perhaps a better example would have been:repeatedconst int[10000] = // loads of dataIn any case, it was not I who claimed that this was a problem, I merelyit. Another poster (forgive me, I can't remember who) was interested inROMstorage 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:-- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/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 changedThis 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_; } // AFTERSimilarly, 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) // AFTERThen, 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
This proposition has merit. Instead of advocating "const" to the language (like a lot of people, including myself until I started to doubt myself, have done) it extends the in/out/inout concept so that it would be semantically as strong as "const" is in C/C++. Just adding const would make the language clunky and complicated; implementing this proposition might be just the right solution. (There are of course the troubles that come with const as well, I think. Something about overload resolution or something. Anyway, it's been one argument against const.) -Antti In article <c94adp$20fe$1 digitaldaemon.com>, Arcane Jill 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).[...](3) As part of Design-By-Contract, to specify a contract that a function will not modify something[...]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:-- I will not be using Plan 9 in the creation of weapons of mass destruction to be used by nations other than the US.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_; } // AFTERSimilarly, 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) // AFTERThen, 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
I'd like to attempt a further clarification regarding the three types of 'const' usage identified: 1) compile-time constants, such as enum. You cannot take the address of these. 2) final instances of arrays, structs, literals, objects, whatever. The memory occupied by these should likely be initialized at the point of declaration, and cannot be changed once set. It is conceivable (and often necessary) to take the address of these, and it might be useful to think of them as ROM candidates. Note that final objects might be a bit tricky. 3) DbC constraints that prevent an argument from being internally modified, either wholly or in specific ways. In addition, there's the issue of pass-by-value/pass-by-reference for structs that muddies the keyword water somewhat within this arena. - Kris "Arcane Jill" <Arcane_member pathlink.com> wrote in message news:c94adp$20fe$1 digitaldaemon.com...I have given a lot of thought to the whole "const" thing, and it seems tomethat there is a way around this that will keep everybody happy. (It wasnot Iwho 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 functionwillnot 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 bedecidedby the compiler. Alas, that may not be so. Given the declaration:caseconst int FORTY_TWO = 42;the expression (&FORTY_TWO) should be illegal in case (1), but legal in(2). Therefore, if we wish the language to support option (2), we need aneed anew keyword. I suggest re-using "final". Thus:cannot take addressconst int FORTY_TWO = 42; // Not stored anywhere, thereforebe taken, but value may not be changedfinal int FORTY_TWO = 42; // Stored in ROM, so address canThis brings us to the can of worms that is definition (3). I believe wecan dothis. 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,shallwork exactly as before (current behavior is unchanged), but new behaviorneedsto be defined for anything declared using "in". I shall describe this new behavior below. Note though that in addition to its placement before formal functionparamters,"in" must also be placeable before member function declarations, likethis:modify any of A's member variablesclass A { in void f() // a contract that the function will notmodify 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 notHOW{ // do stuff } };Above, I talked about a difference in behavior. The important question isthis 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:body ofvoid f(in SomeObject a)the following would all be compile-time errors if they occur within thethe function: (1) a = expression; // illegal always (2) a.m = expression; // illegal always (3) a.g(); // illegal unless g() was declared as in voidg()(4) g(a) // illegal unless g() was declared as voidg(inSomeObject a) These tests are sufficient to catch all contract violations atcompile-time.(The same tests could also be used to verify that "final" objects are not modified). Incidently, the statement:are notthis = 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 westarting from scratch. We already have a large body of code out there,much ofwhich 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:operatorint x() { return x_; } // BEFORE in int x() { return x_; } // AFTERSimilarly, there is an immediate consequence for the non-assigningoverloads, which would also have to be redeclared:everySomeObject opAdd(SomeObject x) // BEFORE in SomeObject opAdd(in SomeObject x) // AFTERThen, similarly, you redeclare every other function that requires it, infile, 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.Providingyou attack the source code in the right order, this should converteverythingthat needs it. And it might even show up a few bugs that got missed alongtheway! CONCLUSION This is achieveable, and simple. The remaining questions are: (i) does theDcommunity want this, given that recoding of a lot of source files will be necessary, and (ii) how does Walter feel about implementing it, given thattheillegalities would be relatively easy to spot? OH YEAH - I FORGOT Given the declaration:callerf(out SomeType x)I would *assume* (though I haven't done the experiment) that either theor the callee overwrites any former value of x with the init value ofSomeType,before doing anything with x. If it doesn't, it should. Arcane Jill
May 27 2004
On Thu, 27 May 2004 08:55:21 +0000 (UTC), Arcane Jill 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.So this idea is a LITERAL value that does not take up any RAM at runtime, right? If so, maybe the keyword 'literal' could be used instead of 'const'. literal FORTY_TWO = 42; Note that the data type information is redundant as it is always the same datatype as the RHS expression. literal PI = 3.1415926535897932384626433832795; Of course if we had array and structure literals this would be nice too. struct Point{ int X,Y;}; literal STARTPOINT = Point(X=5,Y=10); but let's not get ahead of ourselves ;-) -- Derek 28/May/04 10:06:44 AM
May 27 2004
Interesting. I look forward to hearing what Walter thinks of this, but I'm going to keep breathing while I wait. <G> "Arcane Jill" <Arcane_member pathlink.com> wrote in message news:c94adp$20fe$1 digitaldaemon.com...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:cannot take addressconst 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, thereforetaken, but value may not be changedfinal int FORTY_TWO = 42; // Stored in ROM, so address can beThis 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:any of A's member variablesclass A { in void f() // a contract that the function will not modifyany 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 modifynot{ // 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 arestarting 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:Providingint x() { return x_; } // BEFORE in int x() { return x_; } // AFTERSimilarly, 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) // AFTERThen, 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.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
Jun 04 2004