|
From: Andrei Alexandrescu (See Website For Email) on 4 Jan 2008 14:57 I have had a mini- or probably micro- or even milli-epiphany: using "auto" will exacerbate C's broken unsigned arithmetic, which C++ also inherited. As we all know, in C, any expression that has unsigned within a radius of a mile will also have type unsigned. This is a simple rule but one of remarkable bluntness because it assign many operators the wrong result type. Consider u an unsigned int value and i a signed int value. 1. u * i and i * u yield unsigned, although it should yield a signed value. 2. u / i and i / u also yield unsigned, although again they should both return a signed value. 3. u + i and i + u again yield unsigned. Here it is not clear which signedness would be more helpful. In my personal opinion, the tie-breaker should be a rule that "does not yield the wrong result for small, reasonable inputs". I'm basing this on the assumption that most integrals are small in absolute value, something that I recall was measured in the context of conservative garbage collectors. By that rule, u + i and i + u should be typed as signed. Typing them as unsigned make the operation fail for small numbers, e.g. 0 - 1u yields a large unsigned number. 4. This is the funniest one: -u actually returns unsigned! (As an aside to my point: comparisons convert both numbers to unsigned, so i < u will first convert i to unsigned. This again fails for small reasonable numbers because -1 will never be smaller than anything. Some compilers warn about mixed-signed comparisons.) C and C++ partly compensate their mishandling of mixed-sign operations by being generous with implicit conversions: int converts to unsigned and back no problem. So to avoid the whole "I got the wrong signedness" business, you don't even need a cast - only a named value: int a = i + u; // fine unsigned b = i + u; // also fine Complex expressions still are exposed to issues, but they are only a subset of mixed-sign code. Here's an example that might surprise some: int i = -3; unsigned u = 2; int x = (i + u) / 2; The "correct" value is zero, but x receives the largest integer value. This all is hardly news to anyone hanging out around here. My milli-epiphany is that "auto" will make all of the ambiguities worse. Why? Because C and C++98 require a type specification whenever a value is defined. But in C++0x, if auto is successful, people will use "auto" knowing it does the right thing without so much as thinking about it: auto a = i + u; Oops... a will be unsigned, even though the user meant it (and actually, without being an expert in the vagaries of integral arithmetic sincerely thought) it is int. After all, the code: int a = i + u; so it's intuitive that replacing "int" with "auto" is harmless and actually better, because it will nicely become "long" if necessary. So with all things considered, "auto" does not always do the right thing! So I thought I'd share this thought with you all and ask if there are any ideas on how to solve the problem elegantly. My prediction is that, if we keep the current rules, "auto" will actually do more harm than good for mixed-sign arithmetic. As changing semantics is not an option, it might be useful to look into statically disabling certain mixed-sign operations. Andrei -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Francis Glassborow on 4 Jan 2008 22:50 Andrei Alexandrescu (See Website For Email) wrote: > So I thought I'd share this thought with you all and ask if there are > any ideas on how to solve the problem elegantly. My prediction is that, > if we keep the current rules, "auto" will actually do more harm than > good for mixed-sign arithmetic. As changing semantics is not an option, > it might be useful to look into statically disabling certain mixed-sign > operations. > I suppose that we could require a cast when auto is used in the context of a mixed arithmetic initialisation expression. However, as you illustrated, that still leaves the door open for errors. I think I would prefer to strongly encourage compilers to warn any time that mixed mode arithmetic is used in an initialiser expression. That gives me pause for a moments thought, perhaps we could require a cast when using the new initialisation syntax with a mixed mode initialiser expression: int something { u + i}; // error requires that you write it as either int something(u + i); // hopefully generating a compile time warning or int something { int (u + i) }; and: auto something { u + i}; // error requires that you write it as either auto something(u + i); // hopefully generating a compile time warning or auto something { int (u + i) }; -- Note that robinton.demon.co.uk addresses are no longer valid. [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Andrei Alexandrescu (See Website For Email) on 5 Jan 2008 06:32 Francis Glassborow wrote: > Andrei Alexandrescu (See Website For Email) wrote: > >> So I thought I'd share this thought with you all and ask if there are >> any ideas on how to solve the problem elegantly. My prediction is that, >> if we keep the current rules, "auto" will actually do more harm than >> good for mixed-sign arithmetic. As changing semantics is not an option, >> it might be useful to look into statically disabling certain mixed-sign >> operations. >> (All -- sorry for the few incoherent sentences toward the end of my previous message. I haven't proofread it, and it shows.) > I suppose that we could require a cast when auto is used in the context > of a mixed arithmetic initialisation expression. However, as you > illustrated, that still leaves the door open for errors. > > I think I would prefer to strongly encourage compilers to warn any time > that mixed mode arithmetic is used in an initialiser expression. There's a possibility for the compiler to properly track ambiguous-sign results. Imagine the compiler defines internal types intbits and longbits, which mean "value of ambiguous signedness". Then any of the mixed-sign operation yields either intbits or longbits. These types would not be accessible to user code, so if somebody tries to write this: auto a = u + i; they see the error message: "Cannot infer type of a from a value of ambiguous signedness". The beauty of the scheme is that intbits does implicitly convert to int and unsigned int, so as long as the user _does_ decide the desired signedness of the result, the code goes through: int a = u + i; // fine, intbits -> int unsigned b = u + i; // fine, intbits -> unsigned Another nice element of the scheme is that the sign ambiguity is properly taken care of in complex expressions: int a = (u + i) & i; This works because: a) u + i returns intbits b) it's legal to do bitwise AND between intbits and int (the sign is irrelevant) returning intbits c) the intbits result gets converted to a Again, if a were auto, the code would not compile. So in a nutshell the compiler would use these two types to transport the information that ambiguous signedness is in vigor. As soon as the user tries something that has sign-dependent semantics, an error would occur (or warning for legacy code): int a = (u + i ) / 2; // warning: ambiguous-sign operation > That gives me pause for a moments thought, perhaps we could require a > cast when using the new initialisation syntax with a mixed mode > initialiser expression: > > int something { u + i}; // error > requires that you write it as either > > int something(u + i); // hopefully generating a compile time warning > > or > > int something { int (u + i) }; > > and: > > auto something { u + i}; // error > requires that you write it as either > > auto something(u + i); // hopefully generating a compile time warning > > or > > auto something { int (u + i) }; This scheme is imperfect because it requires a cast to an actual type, so the code is brittle when "something" changes type from int to long. There should be library functions that do the cast taking size into account: auto something { std::tosigned (u + i) }; or auto something { std::tounsigned (u + i) }; Andrei -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Nevin :-] Liber on 5 Jan 2008 23:20 In article <477F1858.6090909(a)erdani.org>, "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail(a)erdani.org> wrote: > So I thought I'd share this thought with you all and ask if there are > any ideas on how to solve the problem elegantly. My prediction is that, > if we keep the current rules, "auto" will actually do more harm than > good for mixed-sign arithmetic. As changing semantics is not an option, > it might be useful to look into statically disabling certain mixed-sign > operations. While auto might do "more harm than good" for those folks who mix fundamental types, in my opinion it is more important that the rules for auto remain as close as possible to that of template deduction. Making rules that are inconsistent are the things that make the language more expert friendly at the expense of everyone else, because only the experts will spend enough time learning the deep dark corners of the language to even know about the existence of these tricks. We are talking about people who already mix types without understanding the ramifications of doing so. Do you really expect them to discover this feature of auto? Or worse, their fix might be along the lines of: auto x = static_cast<int>((i + u) / 2); because they "know" that casts always "fix" these kinds of problems. Your change would make the following code transformation very fragile: template<typename T> void func(T const& t) { /* ... */ } //... func(a + b); into template<typename T> void func(T const& t) { /* ... */ } //... auto c = a + b; func(c); Having that exception to the use of auto makes the overall language harder, not easier to use. -- Nevin ":-)" Liber <mailto:nevin(a)eviloverlord.com> 773 961-1620 [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Andrei Alexandrescu (See Website For Email) on 6 Jan 2008 01:48
Nevin :-] Liber wrote: > In article <477F1858.6090909(a)erdani.org>, > "Andrei Alexandrescu (See Website For Email)" > <SeeWebsiteForEmail(a)erdani.org> wrote: > >> So I thought I'd share this thought with you all and ask if there are >> any ideas on how to solve the problem elegantly. My prediction is that, >> if we keep the current rules, "auto" will actually do more harm than >> good for mixed-sign arithmetic. As changing semantics is not an option, >> it might be useful to look into statically disabling certain mixed-sign >> operations. > > While auto might do "more harm than good" for those folks who mix > fundamental types, in my opinion it is more important that the rules for > auto remain as close as possible to that of template deduction. Making > rules that are inconsistent are the things that make the language more > expert friendly at the expense of everyone else, because only the > experts will spend enough time learning the deep dark corners of the > language to even know about the existence of these tricks. This might be a misunderstanding. My idea was to _disable_ "auto" (i.e., render it uncompilable) when combined with certain mixed-sign operations, not to impart to it different semantics than the rest of the type inference mechanism. I agree that making auto smarter than template deduction would be the wrong fight to fight. > We are talking about people who already mix types without understanding > the ramifications of doing so. Do you really expect them to discover > this feature of auto? Or worse, their fix might be along the lines of: > > auto x = static_cast<int>((i + u) / 2); > > because they "know" that casts always "fix" these kinds of problems. People tend to take the path of least resistance. In this case, I think they'd write: int c = (i + u) / 2; which is shorter and does the same thing. > Your change would make the following code transformation very fragile: > > template<typename T> void func(T const& t) { /* ... */ } > //... > func(a + b); > > into > > template<typename T> void func(T const& t) { /* ... */ } > //... > auto c = a + b; > func(c); Nonono, that's certainly not what I had in mind. Again: in my opinion, it might be useful to just disallow (or warn on) the use of auto in the most flagrant cases of unsigned type mishandling. Andrei -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |