Topic: Defect Report: user-defined conversion and built-in operator=


Author: scott douglass <sdouglass@arm.com>
Date: 2000/11/04
Raw View
 [Moderator's note: this defect report has been
 forwarded to the C++ committee. -moderator(fjh).]

After a few postings last month discussing this I'm convinced that I'm
right and most current C++ compilers are violating the standard.  I think
the standard should be changed to do what most current C++ compilers are
doing which is what most users expect.

The current rules for built-in operator= can cause unexpected and unwanted
choices of user-defined conversions to "narrow" built-in types.  By
"narrow" I mean 'bool', 'char', 'signed char', 'unsigned char', 'wchar_t',
'short' and 'unsigned short'.  Unwanted ambiguities can also happen under
the current rules.

Consider, for example:

  struct T {
    operator short() const;
    operator int() const;
  };

  short s;

  void f(const T& t) {
    s = t;  // surprisingly calls T::operator int() const
  }

In this case the standard requires 'T::operator int() const' be used.  The
user probably wants 'T::operator short() const' to be called.

The reason the standard selects 'T::operator int() const' is that 5/3
[expr] says that overloading 13.3.1.2 [over.match.oper] and  13.6
[over.built] determine which user-defined conversions are
selected.  13.6/18 [over.built] says:

>For every triple (L, VQ, R), where L is an arithmetic type, VQ is either
>volatile or empty, and R is a
>promoted arithmetic type, there exist candidate operator functions of the
>form
>
>VQ L& operator=(VQ L&, R);
>[...]

In this example, the best built-in operator= is 'short& operator=(short&,
int)'.  (Since 'R' must be a "promoted arithmetic type" it cannot be
'short'.)  This means that in the example above 't' must be converted to
'int' and 'T::operator int() const' is the best choice to do that.

Similarly in this second example the same choice for built-in operator= is
made and leads to an unwanted ambiguity because neither user-defined
conversion is better:

  struct T2 {
    operator short() const;
    operator char() const;
  };

  short s;

  void f2(const T2& t2) {
    s = t2;  // ambiguous
  }

One possible change that would fix this is to add to 13.6 [over.built]
something like:

 >>
For every pair (T, VQ), where T is an arithmetic type but not a promoted
arithmetic type, VQ is either volatile or empty, there exist candidate
operator functions of the form
         VQ T& operator=(VQ T&, T);
<<

This is analogous to the built-in operator= for enumerations in 13.6/20
[over.built] would solve the problems above but I'm not absolutely sure
that it avoids introducing other unwanted ambiguities.  Some overloading
expert should definitely scrutinize the change -- there may well be a
better change.  Alternatives might include extending the wording in
13.3.1.5 [ovld.match.conv] to include simple assignment.

I have intentionally avoided changing the other assignment operators, e.g.
operator+=.  Although users may expect 'T::operator short() const' to be
called in the following

  void f3(const T& t) {
    s += t;
  }

it would be a bad idea since it would be different than the behavior of
    s = s + t;
which means
    s = (int)s + (int)t;
because the built-in 'int operator+(int, int)' from 13.6/12 [over.built] is
selected.
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Valentin Bonnard <Valentin.Bonnard@free.fr>
Date: 2000/11/04
Raw View
scott douglass wrote:
>
>         [Moderator's note: this defect report has been
>         forwarded to the C++ committee. -moderator(fjh).]

I have also forwarded my comment.

[Note to moderators: what is the general policy for ideas/
solutions/refinement/objections about DR posted in the group ?]

> The current rules for built-in operator= can cause unexpected and unwanted
> choices of user-defined conversions to "narrow" built-in types.  By
> "narrow" I mean 'bool', 'char', 'signed char', 'unsigned char', 'wchar_t',
> 'short' and 'unsigned short'.  Unwanted ambiguities can also happen under
> the current rules.
[...]
> 13.6/18 [over.built] says:
>
> >For every triple (L, VQ, R), where L is an arithmetic type, VQ is either
> >volatile or empty, and R is a
> >promoted arithmetic type, there exist candidate operator functions of the
> >form
> >
> >VQ L& operator=(VQ L&, R);
[...]
> One possible change that would fix this is to add to 13.6 [over.built]
> something like:
>
>  >>
> For every pair (T, VQ), where T is an arithmetic type but not a promoted
> arithmetic type, VQ is either volatile or empty, there exist candidate
> operator functions of the form
>          VQ T& operator=(VQ T&, T);
> <<
[...]
> I have intentionally avoided changing the other assignment operators, e.g.
> operator+=.  Although users may expect 'T::operator short() const' to be
> called in the following
>
>   void f3(const T& t) {
>     s += t;
>   }
>
> it would be a bad idea since it would be different than the behavior of
>     s = s + t;
> which means
>     s = (int)s + (int)t;
> because the built-in 'int operator+(int, int)' from 13.6/12 [over.built] is
> selected.

But the standard doesn't say that x @= y and x = x @ y are equivalent
for builtin types and side-effect free expressions, so I believe that
the fix should apply to all assignment operators.

--

Valentin Bonnard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]
[ Note that the FAQ URL has changed!  Please update your bookmarks.     ]






Author: James Kuyper <kuyper@wizard.net>
Date: 2000/11/04
Raw View
scott douglass wrote:
>
>         [Moderator's note: this defect report has been
>         forwarded to the C++ committee. -moderator(fjh).]
>
> After a few postings last month discussing this I'm convinced that I'm
> right and most current C++ compilers are violating the standard.  I think
> the standard should be changed to do what most current C++ compilers are
> doing which is what most users expect.
>
> The current rules for built-in operator= can cause unexpected and unwanted
> choices of user-defined conversions to "narrow" built-in types.  By
> "narrow" I mean 'bool', 'char', 'signed char', 'unsigned char', 'wchar_t',
> 'short' and 'unsigned short'.  Unwanted ambiguities can also happen under
> the current rules.
>
> Consider, for example:
>
>   struct T {
>     operator short() const;
>     operator int() const;
>   };
>
>   short s;
>
>   void f(const T& t) {
>     s = t;  // surprisingly calls T::operator int() const
>   }
>
> In this case the standard requires 'T::operator int() const' be used.  The
> user probably wants 'T::operator short() const' to be called.
>
> The reason the standard selects 'T::operator int() const' is that 5/3
> [expr] says that overloading 13.3.1.2 [over.match.oper] and  13.6
> [over.built] determine which user-defined conversions are
> selected.  13.6/18 [over.built] says:
>
> >For every triple (L, VQ, R), where L is an arithmetic type, VQ is either
> >volatile or empty, and R is a
> >promoted arithmetic type, there exist candidate operator functions of the
> >form
> >
> >VQ L& operator=(VQ L&, R);
> >[...]
>
> In this example, the best built-in operator= is 'short& operator=(short&,
> int)'.  (Since 'R' must be a "promoted arithmetic type" it cannot be
> 'short'.)  This means that in the example above 't' must be converted to
> 'int' and 'T::operator int() const' is the best choice to do that.

This was a deliberate decision, partly motivated by a desire to remain
compatible with C, but mostly motivated by the same reason that C had
for its type promotion rules. The reason is that most platforms have a
particular integer type that's significantly faster than any smaller
type. That type is supposed to be the one chosen for 'int'. The way
smaller types are often implemented is to load them into registers that
are large enough to store an 'int', perform operations using those
registers, and then squeeze the result back down to the destination size
on output. For the signed types this sometimes involves some expensive
extra instructions to get the same result required by the standard.
Therefore, C/C++ promotes smaller types to 'int' in many contexts. C++
actually makes the promotion less often than C, because of the greater
importance it places on type safety.

If it's important to you that this promotion not occur, you can always
do the following:

 s = static_cast<short>(t);

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]
[ Note that the FAQ URL has changed!  Please update your bookmarks.     ]






Author: scott douglass <sdouglass@arm.com>
Date: 2000/11/06
Raw View
scott douglass wrote:
 > [...]
 > The current rules for built-in operator= can cause unexpected and unwanted
 > choices of user-defined conversions to "narrow" built-in types.  By
 > "narrow" I mean 'bool', 'char', 'signed char', 'unsigned char', 'wchar_t',
 > 'short' and 'unsigned short'.  Unwanted ambiguities can also happen under
 > the current rules.
 >
 > Consider, for example:
 >
 >   struct T {
 >     operator short() const;
 >     operator int() const;
 >   };
 >
 >   short s;
 >
 >   void f(const T& t) {
 >     s = t;  // surprisingly calls T::operator int() const
 >   }
 >
 > In this case the standard requires 'T::operator int() const' be used.  The
 > user probably wants 'T::operator short() const' to be called.
 > [...]

James Kuyper <kuyper@wizard.net> wrote:
>This was a deliberate decision, partly motivated by a desire to remain
>compatible with C, but mostly motivated by the same reason that C had
>for its type promotion rules. The reason is that most platforms have a
>particular integer type that's significantly faster than any smaller
>type. That type is supposed to be the one chosen for 'int'. The way
>smaller types are often implemented is to load them into registers that
>are large enough to store an 'int', perform operations using those
>registers, and then squeeze the result back down to the destination size
>on output. For the signed types this sometimes involves some expensive
>extra instructions to get the same result required by the standard.
>Therefore, C/C++ promotes smaller types to 'int' in many contexts. C++
>actually makes the promotion less often than C, because of the greater
>importance it places on type safety.

I do understand that C invokes the "usual arithmetic conversions" in many
contexts and that the "integral promotions" involved cause narrow
(integral) types to be promoted to 'int'.  And I understand that the
definition of the C++ built-in operators must be crafted to preserve this
behavior -- which is why, for example, there is no 'short
operator+(short,short)'.

But, I don't believe simple assignment invokes the "usual arithmetic
conversions" in C.  In the C standard each operator states what conversions
if any are applied to its operands.
In 6.5.16.1/2 the C99 standard says:
 >>
In simple assignment (=), the value of the right operand is converted to
the type of the
assignment expression and replaces the value stored in the object
designated by the left
operand.
<<
and in 6.5.16/3
 >>
The type of an assignment expression is the type of the left operand unless the
left operand has qualified type, in which case it is the unqualified
version of the type of
the left operand.
<<

In C99, I don't think the result of 'g()' in the following gets converted
to 'int':

         short s2;
         short g(void);
         void h() { s2 = g(); }

[[Aside:  It's not clear to me that the all of the possible conversions
required (e.g. char to short and short to char) are even defined by the
(C99) standard.  The (C99) standard does define the conversions char to int
and int to short and I can't immediately construct a reasonable example
where a direct char to short conversion would give a different result from
the pair of conversions applied in sequence.]]


>If it's important to you that this promotion not occur, you can always
>do the following:
>
>         s = static_cast<short>(t);

I agree this would get the behavior that users expect.  The compilers I
tried gave this behavior even without the cast -- I was surprised that the
(C++) standard required otherwise.  Do you know of any compilers that give
the behavior required by the standard?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]
[ Note that the FAQ URL has changed!  Please update your bookmarks.     ]