From: Andrei Alexandrescu (See Website For Email) on
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
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
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
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
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! ]