.NET Forum / Languages / C# / May 2007
List<> of struct with property. Cannot change value of property. why?
|
|
Thread rating:  |
Zytan - 14 May 2007 18:28 GMT This returns the following error: "Cannot modify the return value of 'System.Collections.Generic.List<MyStruct>.this[int]' because it is not a variable" and I have no idea why! Do lists return copies of their elements? Why can't I change the element itself?
class Program { private struct MyStruct { private int myVar; public int MyProperty { get { return myVar; } set { myVar = value; } } }
private static List<MyStruct> list = new List<MyStruct>();
private static void Main(string[] args) { MyStruct x = new MyStruct(); x.MyProperty = 45; list.Add(x); list[0].MyProperty = 45; // <----------- ERROR HERE } }
Zytan
Bruce Wood - 14 May 2007 18:46 GMT > This returns the following error: > "Cannot modify the return value of > 'System.Collections.Generic.List<MyStruct>.this[int]' because it is > not a variable" > and I have no idea why! Do lists return copies of their elements? Yes. The [] operator on a list is, in fact, a function, so the value stored at that location in the list is returned as a function result, on the stack.
This doesn't cause problems for reference types, because usually you want to change some property of the reference type, so the fact that you get a copy of the reference in the list (not the actual reference that is in the list) doesn't cause problems.
However, for value types exactly the same thing happens, and it does cause problems: the value is copied from the list onto the stack and returned as a function result. Modifying the returned value, of course, has no effect on the contents of the list. The compiler wisely catches this.
> Why can't I change the element itself? > [quoted text clipped - 20 lines] > } > } You need to do this:
MyStruct y = list[0]; y.MyProperty = 45; list[0] = y;
Zytan - 14 May 2007 18:57 GMT > Yes. The [] operator on a list is, in fact, a function, so the value > stored at that location in the list is returned as a function result, > on the stack. Ok.
> This doesn't cause problems for reference types, because usually you > want to change some property of the reference type, so the fact that > you get a copy of the reference in the list (not the actual reference > that is in the list) doesn't cause problems. Right, so that's why it's normally not an issue.
> However, for value types exactly the same thing happens, and it does > cause problems: the value is copied from the list onto the stack and > returned as a function result. Modifying the returned value, of > course, has no effect on the contents of the list. The compiler wisely > catches this. Yup, and structs are value types, so this makes sense.
> You need to do this: > > MyStruct y = list[0]; > y.MyProperty = 45; > list[0] = y; Ok, thanks, Bruce!! This helps a lot!
Zytan
Zytan - 14 May 2007 19:38 GMT > The compiler wisely > catches this. NOTE, the compiler does NOT catch everything! The following code escapes detection:
MyStruct x = new MyStruct(); list[index].MethodThatChangesStructsFields(x);
I guess this could be shortened to:
list[index].MethodThatChangesStructsFields();
And likely THAT escapes detection, as well. Damn! I've been changing all my ararys that are created dynamically (length unknown) into List<>, to avoid Array.Resize.
Zytan
Zytan - 14 May 2007 19:43 GMT > I guess this could be shortened to: > > list[index].MethodThatChangesStructsFields(); Confirmed. This operates on the COPY, and any changes are lost, and the compiler does NOT warn about this.
Zytan
Jon Skeet [C# MVP] - 14 May 2007 19:55 GMT > > I guess this could be shortened to: > > > > list[index].MethodThatChangesStructsFields(); > > Confirmed. This operates on the COPY, and any changes are lost, and > the compiler does NOT warn about this. No, and there's no way it could. There's nothing available to the compiler to let it know that the method changes the contents of the struct.
(Remember when we warned you about making mutable structs, back in March? This is just one of the problems. Just say no to mutable structs, basically :)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Zytan - 14 May 2007 22:44 GMT > No, and there's no way it could. There's nothing available to the > compiler to let it know that the method changes the contents of the > struct. Yes, it could. The compiler knows, at least to some degree, that a method changes the struct that owns it. This is a similar issue to C#'s lack of 'const' for variables and methods. If it had the internal features that checked such things, then it'd know. Then, it could warn that you are changing a struct from a method marked as 'const' (which guarantees to do no such thing), and it could fire a warning for the above case of modifying a temporary.
> (Remember when we warned you about making mutable structs, back in > March? This is just one of the problems. Just say no to mutable > structs, basically :) Yup! :) But, it just doesn't seem fair, since the language isn't quite 'complete', yet. I never had troubles with Lists in C++ like this. But, it's becoming obvious that the c# designers just want us to use reference types when we can, and everything in the langauge is based on this, and thus, it is wildly different than C++ in that regard. I guess there's good and bad that comes with that.
Zytan
Peter Duniho - 14 May 2007 23:11 GMT >> No, and there's no way it could. There's nothing available to the >> compiler to let it know that the method changes the contents of the >> struct. > > Yes, it could. The compiler knows, at least to some degree, that a > method changes the struct that owns it. I'm not sure why you say that. As it's compiling the method, it's true that information is present. But outside of that case, how would the compiler know the method changes the struct? Should it flag all methods that change a struct somehow, and cache that information somewhere for use when compiling other code? Should the linker be required to carry this flag around too, so that when you import a reference to a struct type not compiled with the current project, you still have that information?
> This is a similar issue to > C#'s lack of 'const' for variables and methods. If it had the > internal features that checked such things, then it'd know. I don't see it as "similar". I see it as identical. :)
If C# had "const" for variables and methods, then it would also have the function name decoration that's used to convey that information that C++ has. Then when compiling code that uses a method, *if* the "const" keyword were used, it could know that the method doesn't change the struct.
But then what is the compiler supposed to do with methods not marked as "const"? Should it automatically assume that the method *does* change the struct? And in that case, should the compiler instead of copying the value type create some sort of hidden reference to it on which the method can operate? And if so, why shouldn't the compiler just always do that?
> [...] >> (Remember when we warned you about making mutable structs, back in [quoted text clipped - 3 lines] > Yup! :) But, it just doesn't seem fair, since the language isn't > quite 'complete', yet. IMHO, C# is only "not quite complete" in the same way that C++ and BASIC and FORTRAN are all "not quite complete".
More specifically, it seems to me that C# has pretty well decided what value types are going to do and not do, and it's pretty clear that you don't get to have references to value types. Given the lack of references to value types, I don't see any clean way to have a List<> return a reference to a value type within the list for some method to operate on.
> I never had troubles with Lists in C++ like > this. You would have if there were any implementations of lists in C++ that provided access to list items by value. There may in fact be such an implementation, and if there is, it will have the same issue. It will return a copy of a value within the list, and you can operate on that copy 'till the cows come home and the item in the list will never change.
> But, it's becoming obvious that the c# designers just want us > to use reference types when we can, and everything in the langauge is > based on this, and thus, it is wildly different than C++ in that > regard. I guess there's good and bad that comes with that. I wouldn't agree that the designers want you to use reference types when you can. There's a time and place for a value type, and in fact they even have their place in lists. But it seems to me that other than some semantic oddities (that I've complained about myself :) ), the basic behavior is well-defined.
I agree that this particular example is somewhat confusing. In the same way that overloading the "new" operator bugs me about reference types versus value types, you're dealing with a situation in which the indexing operator "[]" behaves differently depending on whether you've got an array or a List<>. With an array, you get the actual item in the array. With a List<> you get the value from the array, which is always a copy of the item in the array (and if it's a copy of the reference then you can still directly act upon the instance referenced). But once you understand this difference, the underyling "reference vs value" behavior is consistent and reliable.
Pete
Zytan - 14 May 2007 23:23 GMT > > Yes, it could. The compiler knows, at least to some degree, that a > > method changes the struct that owns it. > > I'm not sure why you say that. As it's compiling the method, it's true > that information is present. But outside of that case, how would the > compiler know the method changes the struct? The same way C++ deals with const methods.
> Should it flag all methods > that change a struct somehow, and cache that information somewhere for use > when compiling other code? Yes, but it would seem less silly if there were more reasons to use such information. And having "const" is a large one.
> Should the linker be required to carry this > flag around too, so that when you import a reference to a struct type not > compiled with the current project, you still have that information? I have no idea, but I would assume that a C++ linker does. After all, it's just one bit.
> > This is a similar issue to > > C#'s lack of 'const' for variables and methods. If it had the > > internal features that checked such things, then it'd know. > > I don't see it as "similar". I see it as identical. :)
:)
> If C# had "const" for variables and methods, then it would also have the > function name decoration that's used to convey that information that > C++ has. Then when compiling code that uses a method, *if* the "const" > keyword were used, it could know that the method doesn't change the struct. Yup.
> But then what is the compiler supposed to do with methods not marked as > "const"? Should it automatically assume that the method *does* change the > struct? In c++, if you have a method that doesn't change it, but you don't tell it specifically that it doesn't change it, then the compiler assumes that it COULD change it, and thus doesn't trust that it's being nice. So, it's not that it assumes it does change it, it just assumes that it could change it. And if that's an issue, which it is for a method called on a temporary copy (are these called 'temporaries'?), then the compiler could say, hey, since you didn't say you won't modify this struct, it means you may modify it, and if you do, you're not going to get what you expect. Just a friendly warning.
> And in that case, should the compiler instead of copying the > value type create some sort of hidden reference to it on which the method > can operate? And if so, why shouldn't the compiler just always do that? No, it shouldn't do your work for you. It's just there to notify you when you're being stupid.
> IMHO, C# is only "not quite complete" in the same way that C++ and BASIC > and FORTRAN are all "not quite complete". I agree.
> More specifically, it seems to me that C# has pretty well decided what > value types are going to do and not do, and it's pretty clear that you > don't get to have references to value types. Given the lack of references > to value types, I don't see any clean way to have a List<> return a > reference to a value type within the list for some method to operate on. I am still programming in C++ mode. It's taking me a while to realize that structs become more useless when the language is built mainly around reference types.
> > I never had troubles with Lists in C++ like > > this. [quoted text clipped - 4 lines] > return a copy of a value within the list, and you can operate on that copy > 'till the cows come home and the item in the list will never change. I was referring to the STL, which is part of the (or at least the defacto) standard. It was put together surprisingly well.
> I wouldn't agree that the designers want you to use reference types when > you can. There's a time and place for a value type, and in fact they even > have their place in lists. But it seems to me that other than some > semantic oddities (that I've complained about myself :) ), the basic > behavior is well-defined. I guess at the moment I decided to have a method in my struct, I should have moved to a class.
> I agree that this particular example is somewhat confusing. In the same > way that overloading the "new" operator bugs me about reference types [quoted text clipped - 6 lines] > difference, the underyling "reference vs value" behavior is consistent and > reliable. Yup. thanks for your comments, Pete
Zytan
Jon Skeet [C# MVP] - 14 May 2007 23:44 GMT <snip>
> > I wouldn't agree that the designers want you to use reference types when > > you can. There's a time and place for a value type, and in fact they even [quoted text clipped - 4 lines] > I guess at the moment I decided to have a method in my struct, I > should have moved to a class. No - there are plenty of times it makes sense to have methods in structs. Look at DateTime - loads of methods in there, and it's still a perfectly good value type. It just doesn't have any methods which mutate it.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Zytan - 14 May 2007 23:51 GMT > No - there are plenty of times it makes sense to have methods in > structs. Look at DateTime - loads of methods in there, and it's still a > perfectly good value type. It just doesn't have any methods which > mutate it. Right. So. Structs should be immutable. That's the golden rule? All fields should be private (or readonly) with the constructor setting them?
Zytan
Jon Skeet [C# MVP] - 15 May 2007 00:02 GMT > > No - there are plenty of times it makes sense to have methods in > > structs. Look at DateTime - loads of methods in there, and it's still a > > perfectly good value type. It just doesn't have any methods which > > mutate it. > > Right. So. Structs should be immutable. That's the golden rule? It's at least a silver rule. There may be good reasons to have genuinely mutable structs in very special cases, but I haven't seen them yet.
> All fields should be private (or readonly) with the constructor > setting them? Fields should be private anyway, but yes, they should be private and probably all readonly, unless you want to set them from private methods called in the constructor.
Of course, you might want to have mutable fields for caching purposes (e.g. caching the result of calling GetHashcode) which don't change the perceivable state - no harm in that, although it would be unusual.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jon Skeet [C# MVP] - 14 May 2007 23:47 GMT <snip>
> I agree that this particular example is somewhat confusing. In the same > way that overloading the "new" operator bugs me about reference types > versus value types, you're dealing with a situation in which the indexing > operator "[]" behaves differently depending on whether you've got an array > or a List<>. With an array, you get the actual item in the array. Or to be specific (and odd!) you get the *variable* in the array. That's the weird bit - that an array isn't a collection of values, it's a collection of variables (in the spec, anyway). So
int[] n = new int[4]; is sort of equivalent to:
int n0 = 0; int n1 = 0; int n2 = 0; int n3 = 0;
(except sized at runtime, of course).
That's why you can pass array elements by reference, too. Conceptually slightly odd, IMO.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jon Skeet [C# MVP] - 14 May 2007 23:42 GMT > > No, and there's no way it could. There's nothing available to the > > compiler to let it know that the method changes the contents of the > > struct. > > Yes, it could. The compiler knows, at least to some degree, that a > method changes the struct that owns it. Well, the compiler certainly doesn't know it from the metadata about the method. Are you suggesting that the compiler should start looking at the *implementation* of the method (which could be in a different assembly) to work out what to do? What if the implementation changes?
> This is a similar issue to > C#'s lack of 'const' for variables and methods. If it had the > internal features that checked such things, then it'd know. And then that would show up in the method metadata, yes - but as it is, that information isn't there.
> Then, it could warn that you are changing a struct from a method > marked as 'const' (which guarantees to do no such thing), and it > could fire a warning for the above case of modifying a temporary. Again, if "const" were available, that would make a lot of sense - but it isn't, for better or worse.
> > (Remember when we warned you about making mutable structs, back in > > March? This is just one of the problems. Just say no to mutable [quoted text clipped - 6 lines] > based on this, and thus, it is wildly different than C++ in that > regard. I guess there's good and bad that comes with that. It's wildly different from C++ in many ways. I think those who haven't used C++ to start with actually have an advantage when learning C#, in terms of not having to "unlearn" things.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Christof Nordiek - 15 May 2007 11:52 GMT >> The compiler knows, at least to some degree, that a >> method changes the struct that owns it. [quoted text clipped - 3 lines] > at the *implementation* of the method (which could be in a different > assembly) to work out what to do? What if the implementation changes? If C# had const methods similar to C++, then this could be stored in the metadata. And the compiler could check this while compiling the method. But that certainly would be problematic if different languages are used. Other languages should use the same attribute, and methods from languages, that don't use that same attribute would be regarded as possibly changing the struct. This problem I suppose, doesn't exsist in C++, does it?
BTW How does C++ handle const methods from other modules?
Christof
Peter Duniho - 15 May 2007 18:07 GMT > [...] > BTW How does C++ handle const methods from other modules? As far as I can recall, C++ does not use the "const" information in the way that Zytan is asking for C# to. That is, the "const" attribute of a method or parameters doesn't affect how an item is retrieved from a data structure or how a function is called. It only controls whether the compiler can assume, for the purpose of evaluating "const"-ness in the code being compiled, that the called function doesn't modify the data.
That is, the main reason it's useful in external modules is so that your own modules can use "const" too. :)
If C# *did* have the "const" keyword, I would expect it to work more like that, than in the way that Zytan would like it to work.
Pete
Zytan - 15 May 2007 22:20 GMT > As far as I can recall, C++ does not use the "const" information in the > way that Zytan is asking for C# to. That is, the "const" attribute of a [quoted text clipped - 8 lines] > If C# *did* have the "const" keyword, I would expect it to work more like > that, than in the way that Zytan would like it to work. I think I am saying the same thing.
Once you have this boolean value that states the "const"-ness of a method, then in the case of calling a non-const method on a List<> element that is a temporary (the compiler already knows when it's a temporary), the compiler could complain and say: "Hey, you might be modifying a temporary! You should only call const-methods on temporaries!"
Zytan
Peter Duniho - 15 May 2007 22:45 GMT >> If C# *did* have the "const" keyword, I would expect it to work more >> like that, than in the way that Zytan would like it to work. > > I think I am saying the same thing. Sorry...I guess I misunderstood what you wanted the language to do.
> Once you have this boolean value that states the "const"-ness of a > method, then in the case of calling a non-const method on a List<> > element that is a temporary (the compiler already knows when it's a > temporary), the compiler could complain and say: "Hey, you might be > modifying a temporary! You should only call const-methods on > temporaries!" Well, IMHO the compiler could easily provide the same warning today, without the "const" keyword.
After all, in C++ there's a LOT of code that doesn't modify data that isn't marked "const". Relying on the "const" keyword to enable a warning wouldn't have been a good idea in C++, because you'd get a lot of false positives due to the large amount of code that is "const" without using "const".
Conversely, if such a warning is a good idea (in C# or C++), it seems to me that the compiler ought to just warn and forget about trying to determine whether the method is actually a "const" method or not.
Personall, because of the false positive issue, I think such a warning isn't a good idea. You'd see it far too often when it wasn't legitimate for it to actually be useful.
Now, I suppose the language could add a feature in which it requires the compiler to analyze every method and apply its own "const" attribute based on that analysis. But that opens a whole new can of worms, including what is essentially the same problem that the C++ "const" keyword had: until every single function in the call chain supports that attribute, you wind up with a lot of functions that can't be marked as "const" even though they really are (or conversely, you have to do casting a bunch of stuff to glue the const/not-const stuff together).
My feeling is that while it's possible theoretically to address the issue, I think it's simpler to just make the language simple and consistent, and require developers to understand that the List<> "[]" operator is providing a copy of the element in the list. There's too many situations where working directly with the copied value is useful for the compiler to go around warning you every time you do it, and introducing a "const" attribute (explicit or implicit) creates far too many new hassles to make it worth the trouble.
Opinions will vary, of course. :)
Pete
Ben Voigt - 15 May 2007 23:07 GMT > After all, in C++ there's a LOT of code that doesn't modify data that > isn't marked "const". Relying on the "const" keyword to enable a warning > wouldn't have been a good idea in C++, because you'd get a lot of false > positives due to the large amount of code that is "const" without using > "const". Actually, in a good programmer's hands, that's a notice to the compiler that the function is not guaranteed to leave the data unchanged. I'd hate for the compiler to analyze a stub function, make an automatic determination of const-ness, and somehow affect how the code around it is error-checked. (Note that optimizing the generated machine code is ok, optimizing away errors and warnings is not).
Understanding and using const-correctness is a prerequisite for being a professional C++ programmer. It's an important part of documentation, compile-time error checking, and enables automatic optimizations that couldn't otherwise be performed.
Peter Duniho - 15 May 2007 23:21 GMT > [...] > Understanding and using const-correctness is a prerequisite for being a > professional C++ programmer. I'll buy "understanding". I won't accept "using". Too much professionally-written C++ code (including some of my own) simply does not use "const" for you to make that claim.
Ben Voigt - 16 May 2007 00:37 GMT >> [...] >> Understanding and using const-correctness is a prerequisite for being a [quoted text clipped - 3 lines] > professionally-written C++ code (including some of my own) simply does not > use "const" for you to make that claim. And we're already waaaaay off-topic for the C# group.
I'll just say that you'd have to have very specialized code for const not to be useful. Subsets of C++ used by embedded compilers, perhaps (although in my experience they overload const and make it mandatory). But string literals are arrays of const char, so it's *really* hard to do anything useful without const and maintain any semblance of efficiency.
Peter Duniho - 16 May 2007 08:33 GMT > And we're already waaaaay off-topic for the C# group. No doubt. It happens. :) At least we're still talking about programming. :)
> I'll just say that you'd have to have very specialized code for const > not to be useful. "useful" != "mandatory"
> [...] But string > literals are arrays of const char, so it's *really* hard to do anything > useful without const and maintain any semblance of efficiency. Are you saying that failing to use the "const" keyword in C++ causes performance problems? That's an interesting statement. Care to elaborate?
Pete
Ben Voigt - 16 May 2007 14:38 GMT >Are you saying that failing to use the "const" keyword in C++ causes >performance problems? That's an interesting statement. Care to elaborate? Well, for the specific case of a string literal, you can do:
const char* psz = "This is a constant string";
or
char[] asz = "This is a non-const literal string";
The second form requires the compiler to actually initialize the memory on every entry to the function, instead of referencing a string literal. It also prevents string folding, which increases the size of the binary, which has its own set of performance problems.
Beyond that, pass-by-const reference is more performant than pass-by-value for large data types. Sure, you don't strictly need const for that, you can pass by reference and get most of the performance gains. But, const also enables a lot of optimizations:
reuse of temporaries constructor elision improved alias analysis elimination of variable access loop unrolling
For an example:
const int nibbleCount = sizeof (int) * 2; char formatted[nibbleCount + 1] = { 0 };
char* ToHex(int n) { for( int i = 0; i < nibbleCount; i++ ) { formatted[nibbleCount - i] = 0x30 + ((n >> (4 * i)) & 0x0f); if (formatted[nibbleCount - i] > '9') formatted[nibbleCount - i] += 'a' - '0' - 10; } }
Take out the const, and the snippet won't even compile... and if it did, the compiler couldn't unroll the loop. With a constant value of nibbleCount, however, the compiler can remove the loop entirely, eliminate the variable i, and start using specialized instructions for accessing the different bytes of the input.
Peter Duniho - 16 May 2007 19:48 GMT > Well, for the specific case of a string literal, you can do: > [quoted text clipped - 3 lines] > > char[] asz = "This is a non-const literal string"; A few things.
First, I was under the impression that we were discussing the "const" keyword as it applies to function declarations. The above are examples of "const"-declared constants, which C# does already have, and so aren't really what we were talking about.
Second, in the above example, the second line of code doesn't compile. You can fix the syntax error, by putting the [] in the right place (after the "asz" rather than after the "char"), but then the type of the variable is not the same as the first line of code. You can't compare the results of the two, because you're changing more than just the "const"-ness.
Third, if make the two lines of code actually comparable, by fixing the type in the second line of code to be "char*", you'll find that it compiles to exactly the same instructions as the first line of code. In other words, any difference between the two lines of code you posted (once you fix it so that it compiles) are caused *not* by the use of the "const" keyword, but rather by the differences in the way the compiler deals with a pointer versus an array.
> The second form requires the compiler to actually initialize the memory > on every entry to the function, instead of referencing a string > literal. It > also prevents string folding, which increases the size of the binary, > which has its own set of performance problems. See above. Any such problems are not due to the difference between something being "const" and not "const". (Though, actually...I looked at the code generated and while it's true the array is initialized differently than the pointer, that initialization does not actually involve initializing the storage for the array itself...in other words, it's not like the string literal gets copied).
> Beyond that, pass-by-const reference is more performant than > pass-by-value for large data types. Sure, you don't strictly need > const for that, you can pass by reference and get most of the > performance gains. You don't even not strictly need const for that. The use of the "const" keyword has nothing do with whether you can pass things by reference or not. It makes it safer (which *is* the point of using "const"), but you can pass things by reference just fine without using "const". In other words, in that example, "const" doesn't improve performance, it improves safety.
I certainly wouldn't try to disagree with the statement that "const" improves safety. But that's not the question here.
> But, const also enables a lot of optimizations: > [quoted text clipped - 3 lines] > elimination of variable access > loop unrolling As applied to function declarations, I don't see how "const" enables any of those optimizations. The compiler can't rely on the "const" keyword, because it can always wind up being cast away.
> For an example: > [quoted text clipped - 17 lines] > different > bytes of the input. Well, first of all, that code won't compile regardless. You've got a function that is supposed to return a value, but it doesn't.
Secondly, the reason taking "const" out prevents it from compiling is that you can't initialize an array with a size that isn't known at compile time. And of course, likewise the compiler can't make optimizations requiring constant values based on non-constant values.
For example, these two functions wind up compiled to be very different:
const int i = 5; void Test1() { printf("%d", i); }
int j = 5; void Test2() { printf("%d", j); }
I certainly don't debate that. But the reason they are different is because in the first case, the value of the variable is known at compile time, while the value of the variable in the second case is not. This has exactly nothing to do with the question of using "const" in a function declaration.
I thought maybe you had an example of how including "const" for function declarations in C# would help optimizations. After all, C# already has "const" for variable declarations, and it has the same benefits in C# as it has in C++. So there's not really any point in contrasting the two languages in that way. So far, you haven't provided any actual examples of performance improvements that using "const" in a function declaration would enable.
Pete
Ben Voigt - 17 May 2007 15:32 GMT Sorry about not compile-testing.
Here is an example that may work better for you:
/* in one compilation unit */ namespace { std::vector<int> v; } void stuffit(int& n) { v.push_back(n); }
/* in another compilation unit, assuming that the proper prototype for stuffit is available */ int main() { for( int i = 0; i < 4; i++ ) { stuffit(i); } return 0; }
Without "const", no optimizations are possible. With "const int& n", the compiler can fully unroll the loop and eliminate i. Yes, I know that passing an int by const reference in this case is silly, but it could easily be a large data structure, and stuffit could be an arbitrary level of complexity.
Or:
void demo(int i) { int a[10]; for( int j = 0; j < 10000; j++ ) { a[13 * i % 7 + 3] += j; stuffit(i); } }
With "const int& n", the compiler can determine that i is unchanging for the duration of the loop, and by extension, the pointer a + 13 * i % 7 + 3 can be pre-computed (and in fact the entire addition operation could be pre-evaluated as 10000 * 10001 / 2 => 50005000). Without "const", aliasing analysis reveals that i can change, therefore the computation must be executed inside the loop, with little opportunity for optimization.
And the const on a member function is equivalent to changing constness of the 'this' parameter, so all the above applies.
Ben Voigt - 17 May 2007 15:36 GMT >As applied to function declarations, I don't see how "const" enables any >of those optimizations. The compiler can't rely on the "const" keyword, >because it can always wind up being cast away. I absolutely disagree with this. The onus is on the programmer not to cast away const in any way that would make the compiler's optimizations incorrect which rely on said const-ness. Practically speaking, casting away const is always an error, now that C++ has the mutable keyword. But certainly the compiler can and does rely on the const keyword, and you take your (process) life in your hands when you cast it away.
Zytan - 16 May 2007 17:17 GMT > Actually, in a good programmer's hands, that's a notice to the compiler that > the function is not guaranteed to leave the data unchanged. I'd hate for [quoted text clipped - 7 lines] > compile-time error checking, and enables automatic optimizations that > couldn't otherwise be performed. I got into this a lot back on the VB group, that there's a lot that "const" does that people who have never used it would ever realize. And when I say "used it", i mean actually used it 100% where it should be used. Without getting into detail, I'll mention just one benefit: it's forced me to correct large architectural issues that I wasn't aware of. A lot power in a little word.
I still think C#'s designer's thoughts on how C++'s "const" only works because it can be cast away is 100% completely false. It's done nothing but be a benefit for me. (But, perhaps I just haven't seen the situations he has seen. But, perhaps his situations wouldn't have happened had "const" been used from the get-go, though. Perhaps an architectural redesign wasn't worth having "const" in there, so they cast it away, thinking "const is useless, you have to cast it away to work", and that's the most likely scenario.)
Zytan
Zytan - 16 May 2007 17:12 GMT > Personall, because of the false positive issue, I think such a warning > isn't a good idea. You'd see it far too often when it wasn't legitimate > for it to actually be useful. Yes, definitely for C++, since "const" was never really required.
But, for C#, the warning would only come up when you're potentially modifying a temporary. Usually you don't even want to be calling a method AT ALL on one, so I don't think the warning would happen too often when it shouldn't (but that's based on my limited experience).
> My feeling is that while it's possible theoretically to address the issue, > I think it's simpler to just make the language simple and consistent, and [quoted text clipped - 4 lines] > attribute (explicit or implicit) creates far too many new hassles to make > it worth the trouble. If it happens all the time, and the warning fires false positives, then yes, it would just be a pain. I didn't think that this would really ever happen.
Just an idea. ;)
Zytan
Peter Duniho - 16 May 2007 19:51 GMT > [...] > But, for C#, the warning would only come up when you're potentially > modifying a temporary. Usually you don't even want to be calling a > method AT ALL on one, so I don't think the warning would happen too > often when it shouldn't (but that's based on my limited experience). I disagree that you would never want to call a method on a temporary value. In fact, because of the OOP nature of C#, you are almost always calling a method, if you are using a temporary value at all.
If anything, I could see adding a warning when you are modifying a public field of a value type that is a temporary instance. That seems obviously unwise and reasonable for the compiler to complain. But for methods, the method could be doing anything, and the compiler doesn't really know what that is. In fact, for a well-designed value type (eg immutable), a method on a value type would *always* be safe on a temporary value.
Pete
Ben Voigt - 17 May 2007 15:37 GMT >> [...] >> But, for C#, the warning would only come up when you're potentially [quoted text clipped - 12 lines] > that is. In fact, for a well-designed value type (eg immutable), a method > on a value type would *always* be safe on a temporary value. And an immutable value type would only have methods declared const, so safety is properly noted by the compiler.
> Pete Zytan - 15 May 2007 15:13 GMT > Well, the compiler certainly doesn't know it from the metadata about > the method. Are you suggesting that the compiler should start looking > at the *implementation* of the method (which could be in a different > assembly) to work out what to do? What if the implementation changes? No, I am not suggesting that.
I am suggesting that it could do it in the same way C++ does it. C++ solved it, so it's solvable. Likely, it's stored in the "metadata" whatever that means, presumably in the information that it arrives at when compiling the method, so that someone else who uses it doesn't have to look at the implementation, just the analysis.
> And then that would show up in the method metadata, yes - but as it is, > that information isn't there. I know it lacks this information. But, it could do it, there's no doubt.
> Again, if "const" were available, that would make a lot of sense - but > it isn't, for better or worse. Yup.
> It's wildly different from C++ in many ways. I think those who haven't > used C++ to start with actually have an advantage when learning C#, in > terms of not having to "unlearn" things. I think I like that C++ is a little closer to reality, that is, how the computer does things. C# is a little bit removed. Each has its advantages. To some degree, a good program / programmer needs both. So, you shouldn't have to "unlearn" anything, but you do only because it hides the truth. Of course, C++ does it's fair share of hiding things, too, so an identical argument could be made on C++ vs assembly.
Zytan
Peter Duniho - 15 May 2007 18:10 GMT > [...] > I am suggesting that it could do it in the same way C++ does it. C++ > solved it, so it's solvable. Well, note that C++ implements it by including the "const" right in the function name. Also note that C++ doesn't use "const" in the way that you'd like C# to use it (see my previous post). I'm not sure it's fair to say that "C++ solved it", since C++ doesn't really solve the problem we started out talking about.
Frankly, I think C# already has enough implicit code-generation gotchas as it is. Things like overloading hiding certain type conversions, for example. I doubt that even if C# had a "const" operator, I'd want it accessing elements in a List<> differently depending on whether I was calling a method immediately on the item, and whether that method was labeled "const".
Pete
Zytan - 15 May 2007 22:25 GMT > Well, note that C++ implements it by including the "const" right in the > function name. Also note that C++ doesn't use "const" in the way that > you'd like C# to use it (see my previous post). I'm not sure it's fair to > say that "C++ solved it", since C++ doesn't really solve the problem we > started out talking about. I think we are speaking about the same thing, so perhaps you misunderstand my idea. I just thought if C# had "const" like C++ does, then it could go one step further, and make use of it to show a warning that you are calling a non-const (i.e. potentially modifying) method on a temporary, which seems pointless.
> Frankly, I think C# already has enough implicit code-generation gotchas as > it is. Things like overloading hiding certain type conversions, for > example. I doubt that even if C# had a "const" operator, I'd want it > accessing elements in a List<> differently depending on whether I was > calling a method immediately on the item, and whether that method was > labeled "const". No, I wouldn't want things to change depending if it had const-ness or not! It could just be used for a helpful warning, that's all: Why call a method that changes the class when the class is a temporary, and is thrown away before you can even see or use it?
Zytan
Peter Duniho - 15 May 2007 22:50 GMT > I think we are speaking about the same thing, so perhaps you > misunderstand my idea. I just thought if C# had "const" like C++ > does, then it could go one step further, and make use of it to show a > warning that you are calling a non-const (i.e. potentially modifying) > method on a temporary, which seems pointless. I think I cover this adequately in the article I just posted, but just for completeness...
The problem (well, "a problem" anyway) is that lots of methods are essentially "const" even though no one's bothered to mark them as such. For that matter, lots of methods are essentially "const" even though they can't be marked as such, because they might call some other code that is essentially "const" but which isn't marked as such.
I suppose you could work it the other way around, claiming that code is "const" unless otherwise marked (say, introduce an "unconst" keyword you have to use any time you want a method to be able to change things). But then you'd have the language assuming a bunch of external functions are const even though they are not (or if the language doesn't do that, you wind up with the previous problem *plus* an inconsistency in how functions are treated).
> No, I wouldn't want things to change depending if it had const-ness or > not! It could just be used for a helpful warning, that's all: Why > call a method that changes the class when the class is a temporary, > and is thrown away before you can even see or use it? Well, a *class* _isn't_ temporary. If you get a reference from a List<>, then yes you get a copy of the reference, just like you get a copy of a value type. But the reference refers to a single instance, and if you change that instance, that change is reflected whether you look at the copied reference you're using, or the original reference stored in the List<>.
Pete
Zytan - 16 May 2007 17:23 GMT > I think I cover this adequately in the article I just posted, but just for > completeness... Yes, sorry to continue this in 2 different sections.
> The problem (well, "a problem" anyway) is that lots of methods are > essentially "const" even though no one's bothered to mark them as such. Yes, but you could mark them as "const" when the warning fires.
> For that matter, lots of methods are essentially "const" even though they > can't be marked as such, because they might call some other code that is > essentially "const" but which isn't marked as such. Yes, and this forces you to mark these as "const". And if, finally you realize at the very end you can't change the last one, since it ISN'T const, and it should be, and you fix a major design issue.
> > No, I wouldn't want things to change depending if it had const-ness or > > not! It could just be used for a helpful warning, that's all: Why [quoted text clipped - 7 lines] > copied reference you're using, or the original reference stored in the > List<>. Sorry, you're right, classes are references, and the 'copy' is the reference to it, so you still have access to the original. And a 'class' is not a temporary, the reference to it is.
I *meant* to say 'struct' / value type, instead. I'll try to be more clear in the future.
Zytan
Peter Duniho - 16 May 2007 19:55 GMT >> The problem (well, "a problem" anyway) is that lots of methods are >> essentially "const" even though no one's bothered to mark them as such. > > Yes, but you could mark them as "const" when the warning fires. Been there, done that. In any sizable project, you can waste a whole day chasing down warnings and still not find all the places you need to add "const".
And even when you spend that time, frequently you reach a point where you're calling some third-party API that you don't have the freedom to change. At that point, if the language doesn't allow you to cast away const-ness, you're stuck. And allowing you to cast away const-ness significantly lessens the usefulness of "const", IMHO.
> Yes, and this forces you to mark these as "const". And if, finally > you realize at the very end you can't change the last one, since it > ISN'T const, and it should be, and you fix a major design issue. What do you do when you don't have control over the design that is causing you trouble?
>> > No, I wouldn't want things to change depending if it had const-ness or >> > not! It could just be used for a helpful warning, that's all: Why [quoted text clipped - 5 lines] > I *meant* to say 'struct' / value type, instead. I'll try to be more > clear in the future. Okay...well, I can agree that calling a method that changes a struct when the struct is temporary isn't useful. But how to implement this in a practical way is the question.
Pete
Ben Voigt - 17 May 2007 15:41 GMT >>> The problem (well, "a problem" anyway) is that lots of methods are >>> essentially "const" even though no one's bothered to mark them as such. [quoted text clipped - 8 lines] > you're calling some third-party API that you don't have the freedom to > change. At that point, if the language doesn't allow you to cast away And that impeaches the design of said API and should seriously call into question your decision to use it. If a library designer can't get const-correctness, how can they be expected to properly solve a bunch of more devious concerns (parameter checking, buffer overflows, race conditions, etc)?
> const-ness, you're stuck. And allowing you to cast away const-ness > significantly lessens the usefulness of "const", IMHO. [quoted text clipped - 5 lines] > What do you do when you don't have control over the design that is causing > you trouble? You let your boss know that the design has fundamental flaws, and be thankful that const brought these to your attention before deploying said flawed design in a critical scenario and maybe killing someone.
>>> > No, I wouldn't want things to change depending if it had const-ness or >>> > not! It could just be used for a helpful warning, that's all: Why [quoted text clipped - 11 lines] > > Pete Peter Duniho - 17 May 2007 17:35 GMT >> And even when you spend that time, frequently you reach a point where >> you're calling some third-party API that you don't have the freedom to >> change. At that point, if the language doesn't allow you to cast away > > And that impeaches the design of said API and should seriously call into > question your decision to use it. You mean, like the Windows API?
I suppose I could avoid writing Windows software, but...that severely restricts my market.
Pete
Ben Voigt - 17 May 2007 19:17 GMT >>> And even when you spend that time, frequently you reach a point where >>> you're calling some third-party API that you don't have the freedom to [quoted text clipped - 4 lines] > > You mean, like the Windows API? You're going to have to be far more specific -- there are a lot of different libraries grouped under the heading of "the Windows API" and all the core ones are const-correct, in my experience.
If you find a Windows API function that isn't const-correct, then yes, I would avoid that library and re-implement the required functionality using the core APIs (or find a solution that has already done so, in a trustworthy manner).
> I suppose I could avoid writing Windows software, but...that severely > restricts my market. > > Pete Peter Duniho - 14 May 2007 19:46 GMT > [...] > I guess this could be shortened to: [quoted text clipped - 4 lines] > all my ararys that are created dynamically (length unknown) into > List<>, to avoid Array.Resize. Well, you can still apply the same technique Bruce mentions:
MyStruct x = list[index];
x.MethodThatChangesStructsFields(); list[index] = x;
Pete
Samuel R. Neff - 14 May 2007 20:25 GMT If you declare your structure to implement an interface and then declare the list as containing the interface instead of the structure, then you can work with the structure (through the interface implemented members) directly (interfaces allow you to pierce the box).
Code sample below.
HTH,
Sam
------------------------------------------------------------ We're hiring! B-Line Medical is seeking .NET Developers for exciting positions in medical product development in MD/DC. Work with a variety of technologies in a relaxed team environment. See ads on Dice.com.
public static void Test() { MyClass[] classArray = new MyClass[1]; MyStruct[] structArray = new MyStruct[1]; MyInterface[] interfaceArray = new MyInterface[1]; List<MyClass> classList = new List<MyClass>(1); List<MyStruct> structList = new List<MyStruct>(1); List<MyInterface> interfaceList = new List<MyInterface>(1);
classArray[0] = new MyClass(); classList.Add(new MyClass()); interfaceArray[0] = new MyStruct(); structList.Add(new MyStruct()); interfaceList.Add(new MyStruct());
classArray[0].X++; interfaceArray[0].X++; structArray[0].X++; classList[0].X++;
// Cannot modify the return value of 'List<MyStruct>.this[int]' // because it is not a variable //structList[0].X++; interfaceList[0].X++;
Console.WriteLine("Class Array : " + classArray[0].X); Console.WriteLine("Struct Array : " + structArray[0].X); Console.WriteLine("Interface Array : " + interfaceArray[0].X); Console.WriteLine("Class List : " + classList[0].X); Console.WriteLine("Struct List : " + structList[0].X); Console.WriteLine("Interface List : " + interfaceList[0].X); } }
public class MyClass : MyInterface { private int x;
public int X { get { return x; } set { x = value; } } }
public struct MyStruct : MyInterface { private int x;
public int X { get { return x; } set { x = value; } } }
public interface MyInterface { int X { get; set; } }
>This returns the following error: >"Cannot modify the return value of [quoted text clipped - 27 lines] > >Zytan Bruce Wood - 14 May 2007 21:31 GMT > If you declare your structure to implement an interface and then > declare the list as containing the interface instead of the structure, [quoted text clipped - 90 lines] > > } Now I'm curious: does the array MyInterface[] contain the actual values, or does one incur boxing overhead when putting the value into the array? In other words, is it an array of MyStruct, or an array of boxes of MyStructs?
Jon Skeet [C# MVP] - 14 May 2007 22:11 GMT > Now I'm curious: does the array MyInterface[] contain the actual > values, or does one incur boxing overhead when putting the value into > the array? In other words, is it an array of MyStruct, or an array of > boxes of MyStructs? Boxes - the values are references, and have to be, given that you could use a reference type implementation.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Samuel R. Neff - 14 May 2007 22:27 GMT They are boxes, but it's not 100% the same as boxing in other situations. Because of the interface they are boxed once and can therefore be manipulated within the box, so you can do things to them without unboxing. So you get boxing overhead possibly without unboxing overhead.
For code clarity and consistency I would lean towards using immutable structures, but it's an interesting technical clarification. :-)
Sam
------------------------------------------------------------ We're hiring! B-Line Medical is seeking .NET Developers for exciting positions in medical product development in MD/DC. Work with a variety of technologies in a relaxed team environment. See ads on Dice.com.
>> If you declare your structure to implement an interface and then >> declare the list as containing the interface instead of the structure, [quoted text clipped - 7 lines] >> >> Sam ...
>Now I'm curious: does the array MyInterface[] contain the actual >values, or does one incur boxing overhead when putting the value into >the array? In other words, is it an array of MyStruct, or an array of >boxes of MyStructs? Jon Skeet [C# MVP] - 14 May 2007 22:33 GMT > They are boxes, but it's not 100% the same as boxing in other > situations. Because of the interface they are boxed once and can > therefore be manipulated within the box, so you can do things to them > without unboxing. So you get boxing overhead possibly without > unboxing overhead. It's worth clarifying here that the boxing itself is exactly the same as normal. Indeed, you can take something that has been boxed in a different way and cast it to the interface:
object o = 5; // Normal boxing IComparable c = (IComparable)o; // Normal reference cast
> For code clarity and consistency I would lean towards using immutable > structures, but it's an interesting technical clarification. :-) Agreed :)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Christof Nordiek - 15 May 2007 12:02 GMT > They are boxes, but it's not 100% the same as boxing in other > situations. Because of the interface they are boxed once and can [quoted text clipped - 4 lines] > For code clarity and consistency I would lean towards using immutable > structures, but it's an interesting technical clarification. :-) Yes, but unboxing had much less overhead. IIUC it's only one additional indirection while fetching the value. No object creation there.
Christof
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|