Topic: a[i] === *(a+i) for used defined types?


Author: "Yaroslav Mironov" <tada@mail.wplus.net>
Date: Sun, 15 Oct 2000 03:36:36 GMT
Raw View
Hi

5.2.1 of the standard defines the equivalence of a[i] and *(a+i) when one of
the operands is of a pointer type and another is of enumeration or integral
type.

However, Borland C++ 5.5 supports this equivalence for any type a that has
an operator+(type_of_i) and operator*() for type a.

For example:

struct A
{
 A operator+(int);
 int& operator*();
};

A operator+(int, A);
A operator+(const char *, A);

int main()
{
 A a;

 a[1] = 0; // line 1
 int i = a[4]; // line 2

 i = 3[a]; // line 3
 i = "foobar"[a]; // line 4
}

The only error occures in line 4, when the compiler refuses to call
operator+(const char*, B). I think it is because the built-in subscription
operator is preferred (why?) over the user defined operator+.

Other compilers (GCC, MSVC) produce various range of errors for each of the
four expressions in lines 1-4.

The standard never says anything about the equivalence of a[i] and *(a+i)
for user defined classes. It seems to me that this behavior of BCC is
somewhat confusing. Sometimes it leads to strange errors when misusing
subscripts.

But on the other hand, there should be some reasons why Borland has chosen
this behavior. Although I can't see them.

Comments?

Yaroslav


---
[ 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/10/15
Raw View
Yaroslav Mironov wrote:
>
> Hi
>
> 5.2.1 of the standard defines the equivalence of a[i] and *(a+i) when one of
> the operands is of a pointer type and another is of enumeration or integral
> type.
>
> However, Borland C++ 5.5 supports this equivalence for any type a that has
> an operator+(type_of_i) and operator*() for type a.
>
> For example:
>
> struct A
> {
>  A operator+(int);
>  int& operator*();
> };
>
> A operator+(int, A);
> A operator+(const char *, A);
>
> int main()
> {
>  A a;
>
>  a[1] = 0; // line 1
>  int i = a[4]; // line 2
>
>  i = 3[a]; // line 3
>  i = "foobar"[a]; // line 4
> }
>
> The only error occures in line 4, when the compiler refuses to call
> operator+(const char*, B). I think it is because the built-in subscription
> operator is preferred (why?) over the user defined operator+.

>
> Other compilers (GCC, MSVC) produce various range of errors for each of the
> four expressions in lines 1-4.
>
> The standard never says anything about the equivalence of a[i] and *(a+i)
> for user defined classes. It seems to me that this behavior of BCC is

Section 13.5p7 says "The identities among certain predefined operators
applied to basic types (for example, ++a = a+=1) need not hold for
operator functions". The identity that a[i] == *(a+i) is just another
example.

I believe that this statement is meant to make explicit the fact that
users are not required to enforce those identities. I don't think that
implementations are allowed to do what you're suggesting, with one key
exception:

When your code uses operator[]() when there is no matching overload of
operator[]() in scope, the code is ill formed. However, after issuing
the diagnostic, an implementation is always free to accept erroneous
code, and do something useful with it. Therefore, if an implementation
implicitly declares and implicitly defines operator[]() only if it is
used in context where the lack of such a definition makes the code
ill-formed, then this extension would be perfectly conforming to the
standard. Does Borland produce a diagnostic message?

On the other hand, if the implicit operator[]() conflicts with a
pre-defined or user-defined operator[](), and therefore changes the
behavior of strictly conforming code, then the implementation is
non-conforming. This will be the case if:

a) the implicit operator[]() is treated as if it's declared in the same
scope as a user-defined operator[]() of the same signature, thereby
triggering rejection of the code.

b) the implicit operator[]() is treated as though it were declared in a
scope which hides a conflicting definition in an enclosing scope.

c) the implicit operator[]() is a better match than the one which should
have been selected by overload resolution. Example:

struct Base
{
 int b;
 int d;

 Base(int i=0):b(i),d(0) {}
 Base operator+(int i) { return Base(b+j); }
 int& operator*() { return b};
 int& operator[](int i){ return d};
 // Note: x[i] != *(x+i)
};

struct Derived : public Base
{
 Derived(int i=0, j=0):b(i),d(j) {}
 Derived operator+(int j) { return Derived(b,j); }
 int& operator*() { return b+d};
};

If that compiler implicitly defines a Derived::operator[](int), it will
be preferred over Base::operator[](int) in some contexts, and will
therefore do the wrong thing.

> somewhat confusing. Sometimes it leads to strange errors when misusing
> subscripts.
>
> But on the other hand, there should be some reasons why Borland has chosen
> this behavior. Although I can't see them.

Users are used to the standard identities, and when it's appropriate for
a UDT to support those identities, they get annoyed by the necessity of
writing extra overloads to enforce them. I certainly do. There's
something to be said for this idea, but like all extensions it can trap
the user into writing non-portable code.

Here's list of the identities I can think of that could reasonably be
enforced by implicit definitions. How many of them does Borland support?

 a[i]  *(a + i)
 a->b  (*a).b
 +a  a
 -a  0 - a
 ++a  a += 1   (similarly for --a)
 a++  (temp = a, ++a, temp) (similarly for a--)
 a % b  a - b * (a / b)
 a * b  b * a   (similarly for a+b)
 a - b  -(b - a)
 a > b  b < a   (or vice-versa)
 a <= b  !(a > b)  (or vice-versa;
       similarly for a>=b)
 a == b  (a <= b)&&(b <= a)
 a != b  !(a == b)  (or vice-versa)
 a | b  ~(~a & ~b)  (or vice-versa)
 a || b  !(!a && !b)  (or vice-versa)
 a _op_= b a = a _op_ b
 a,b  b

---
[ 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.     ]