Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

. and ->: Do they need to be distinct?

336 views
Skip to first unread message

Seebs

unread,
Mar 14, 2013, 1:56:47 PM3/14/13
to
After a fascinating conversation with a friend who comes from a very different
language background than I do, I have realized a thing:

Imagine a language otherwise like C, in which the "." operator can take either
a struct or a pointer-to-struct, and does the same thing either way -- obtains
the named member from the struct. In short, if it wouldn't compile now, but
it would have if it had been a ->, it compiles as if it were ->.

I've been thinking about this, and the thing is, it bothers me, because I
think of these as very different classes of things. But I can't think of any
case in which this would, or even *could*, introduce an ambiguity into the
meaning of any program. If there were simply only one operator for these,
instead of two, it wouldn't even create confusion of intent.

I am not sure I'd like this, because it would (in C as it exists now) indicate
a confusion of levels. But if it had always been that way, I don't think
that would be a problem. So, I don't think this would be an entirely
desireable change now, but I'm curious as to whether there's a problem with it
I haven't thought of.

-s
--
Copyright 2013, all wrongs reversed. Peter Seebach / usenet...@seebs.net
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
I am not speaking for my employer, although they do rent some of my opinions.

Keith Thompson

unread,
Mar 14, 2013, 2:30:14 PM3/14/13
to
Seebs <usenet...@seebs.net> writes:
> After a fascinating conversation with a friend who comes from a very
> different language background than I do, I have realized a thing:
>
> Imagine a language otherwise like C, in which the "." operator can
> take either a struct or a pointer-to-struct, and does the same thing
> either way -- obtains the named member from the struct. In short, if
> it wouldn't compile now, but it would have if it had been a ->, it
> compiles as if it were ->.
>
> I've been thinking about this, and the thing is, it bothers me,
> because I think of these as very different classes of things. But I
> can't think of any case in which this would, or even *could*,
> introduce an ambiguity into the meaning of any program. If there were
> simply only one operator for these, instead of two, it wouldn't even
> create confusion of intent.
>
> I am not sure I'd like this, because it would (in C as it exists now)
> indicate a confusion of levels. But if it had always been that way, I
> don't think that would be a problem. So, I don't think this would be
> an entirely desireable change now, but I'm curious as to whether
> there's a problem with it I haven't thought of.

One data point: Ada does exactly that. Foo.Bar is valid if Foo
is either a record (struct) with a member named Bar, or if Foo
is a pointer to such a record. It goes back more than 30 years,
and I'm not aware that it's caused any problems.

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Shao Miller

unread,
Mar 14, 2013, 3:24:46 PM3/14/13
to
On 3/14/2013 13:56, Seebs wrote:
> After a fascinating conversation with a friend who comes from a very different
> language background than I do, I have realized a thing:
>
> Imagine a language otherwise like C, in which the "." operator can take either
> a struct or a pointer-to-struct, and does the same thing either way -- obtains
> the named member from the struct. In short, if it wouldn't compile now, but
> it would have if it had been a ->, it compiles as if it were ->.
>
> I've been thinking about this, and the thing is, it bothers me, because I
> think of these as very different classes of things. But I can't think of any
> case in which this would, or even *could*, introduce an ambiguity into the
> meaning of any program. If there were simply only one operator for these,
> instead of two, it wouldn't even create confusion of intent.
>
> I am not sure I'd like this, because it would (in C as it exists now) indicate
> a confusion of levels. But if it had always been that way, I don't think
> that would be a problem. So, I don't think this would be an entirely
> desireable change now, but I'm curious as to whether there's a problem with it
> I haven't thought of.
>

Interesting...

A quirk would be:

typedef struct { int x; } foo_t;

foo_t arr[] = { { 42 }, { 13 }, { 10 }, { 0 } };

arr.x;
arr[0].x;

would make me blink. (But possibly because of conditioning, as you say.)

--
- Shao Miller
--
"Thank you for the kind words; those are the kind of words I like to hear.

Cheerily," -- Richard Harter

Shao Miller

unread,
Mar 14, 2013, 3:34:24 PM3/14/13
to
On 3/14/2013 13:56, Seebs wrote:
> After a fascinating conversation with a friend who comes from a very different
> language background than I do, I have realized a thing:
>
> Imagine a language otherwise like C, in which the "." operator can take either
> a struct or a pointer-to-struct, and does the same thing either way -- obtains
> the named member from the struct. In short, if it wouldn't compile now, but
> it would have if it had been a ->, it compiles as if it were ->.
>
> I've been thinking about this, and the thing is, it bothers me, because I
> think of these as very different classes of things. But I can't think of any
> case in which this would, or even *could*, introduce an ambiguity into the
> meaning of any program. If there were simply only one operator for these,
> instead of two, it wouldn't even create confusion of intent.
>
> I am not sure I'd like this, because it would (in C as it exists now) indicate
> a confusion of levels. But if it had always been that way, I don't think
> that would be a problem. So, I don't think this would be an entirely
> desireable change now, but I'm curious as to whether there's a problem with it
> I haven't thought of.
>

How far up would it carry?

typedef struct { int x; } foo_t;

foo_t o = { 42 }, * p = &o, ** pp = &p;

pp.x;

Tim Rentsch

unread,
Mar 15, 2013, 10:49:27 PM3/15/13
to
Seebs <usenet...@seebs.net> writes:

> After a fascinating conversation with a friend who comes from a
> very different language background than I do, I have realized a
> thing:
>
> Imagine a language otherwise like C, in which the "." operator can
> take either a struct or a pointer-to-struct, and does the same
> thing either way -- obtains the named member from the struct. In
> short, if it wouldn't compile now, but it would have if it had
> been a ->, it compiles as if it were ->. [snip elaboration]

In earlier (definitely pre-K&R) versions of C, the distinction
may have been necessary, because the compilers didn't pay as much
attention to types.

In modern C, because type information is available the distinction
becomes redundant. Indeed there is an analogous case where this
kind of redundancy has been exploited, namely in calling through
a pointer to function, eg, int (*fp)( int, int ), which can be
used in an expression just like an ordinary function:

blah = fp( 1, 2 );

rather than

blah = (*fp)( 1, 2 );

Clearly a similar kind of transformation could be made with -> and .

JSmith

unread,
Mar 16, 2013, 7:21:43 AM3/16/13
to
On Mar 15, 10:49 pm, Tim Rentsch <t...@alumni.caltech.edu> wrote:
Exactly. This is a relic of the very earliest versions of C. and is
unnecessary. Essentially all languages that are derived from the C
syntax have discarded it. I (and many others) have suggested for years
that this be changed, but it has never become a priority for the
committee.

This is not just a quirk. There are situations where the syntax makes
maintenance awkward. For example, when a struct is initially declared
as static or auto, and then changed to dynamically allocated as the
design evolves, the syntax of all references must be changed.

Andy Walker

unread,
Mar 16, 2013, 9:34:56 AM3/16/13
to
On 14/03/13 17:56, Seebs wrote:
> Imagine a language otherwise like C, in which the "." operator can take either
> a struct or a pointer-to-struct, and does the same thing either way [...].

KeithT has already mentioned Ada. As another data point, Algol 68
uses the pseudo-operator "OF" for all selection, whether from structure
constants, variables, pointers or arrays.

Thus, in the context of

MODE PERSON = STRUCT (STRING name, INT age, REF PERSON mother, father);

"age OF p" is an integer [constant] if "p" is a "PERSON" [constant], an
integer variable [REF INT] if "p" is a variable or pointer, an array of
integers if "p" is an array of people, and an array of integer variables
if "p" is a variable array or a pointer to such an array. Similarly,
"mother OF p" is a person variable, a pointer to such a variable, or
an array depending on "p".

It's been working without a hitch for nearly 45 years [and still
counting], and it's very convenient.

--
Andy Walker,
Nottingham.

Shao Miller

unread,
Mar 16, 2013, 10:10:18 AM3/16/13
to
How many levels of indirection should such a convenience be willing to
travel?

typedef struct { int x; } foo_t;
...
void func1(void) {
foo_t **** foo = ...;

if (foo && *foo && **foo && ***foo && foo.x)
...
}

> This is not just a quirk. There are situations where the syntax makes
> maintenance awkward. For example, when a struct is initially declared
> as static or auto, and then changed to dynamically allocated as the
> design evolves, the syntax of all references must be changed.
>

If you want consistency of operator, you can adhere to a convention of
declaring structures/unions as arrays of one, including members of
structures/unions.

typedef struct { foo_t foo[1]; int y; } bar_t;
typedef union { bar_t bar[1]; double d; } baz_t;

static baz_t DefaultBaz[1];

void func2(void) {
baz_t baz1[1];
baz_t * baz2;

baz2 = malloc(sizeof *baz2);
if (!baz2)
return;

*baz1 = *DefaultBaz;
baz1->bar->foo->x = 42;

*baz2 = *baz1;
baz2->bar->foo->x++;
baz2->bar->y++;
...
}

If you decide one day that the 'bar' member of 'baz_t' should actually
be a pointer because all bars should be allocated through API, then at
least you don't have to replace '.' with '->' all over the place.
(Though you still might need to sprinkle in assertions or other null
pointer checks, if there are integrity concerns.)

Wojtek Lerch

unread,
Mar 16, 2013, 12:49:02 PM3/16/13
to
On 16/03/2013 7:21 AM, JSmith wrote:
> Exactly. This is a relic of the very earliest versions of C. and is
> unnecessary. Essentially all languages that are derived from the C
> syntax have discarded it.

Um does C++ not count? My C++ is a little rusty, but doesn't "x->y"
have a completely different meaning in C++ when x is a class object
rather than a pointer?

James Kuyper

unread,
Mar 16, 2013, 1:54:01 PM3/16/13
to
Many operators have different meanings in C++ when the operator is
overloaded, there's nothing special about -> in that regard. However, if
x is a class object for which no such operator overload has been
defined, x->y is simply a syntax error. If it is overloaded, it must be
overloaded in such a way that (x.operator->())->y is a valid operation.
The second "->" in that expression could recursively involve an operator
overload, but somewhere along the line the recursion has to be
terminated by an operator->() that returns a pointer to a type with a
member named 'y'.

Note that the member selection operator itself, "." cannot be overloaded.
--
James Kuyper

Wojtek Lerch

unread,
Mar 16, 2013, 2:24:28 PM3/16/13
to
On 16/03/2013 1:54 PM, James Kuyper wrote:
> On 03/16/2013 12:49 PM, Wojtek Lerch wrote:
>> On 16/03/2013 7:21 AM, JSmith wrote:
>>> Exactly. This is a relic of the very earliest versions of C. and is
>>> unnecessary. Essentially all languages that are derived from the C
>>> syntax have discarded it.
>>
>> Um does C++ not count? My C++ is a little rusty, but doesn't "x->y"
>> have a completely different meaning in C++ when x is a class object
>> rather than a pointer?
>
> Many operators have different meanings in C++ when the operator is
> overloaded, there's nothing special about -> in that regard.

It is a little special, if you want to change the language by declaring
that x->y is, by definition, equivalent to (&x)->y. In C++, that would
change the meaning of some valid programs.

> Note that the member selection operator itself, "." cannot be overloaded.

Or applied to pointers. That's why it would be OK for C++ to make x.y
equivalent to (*x).y when x is a pointer. But that's not the case with
the -> operator.



Wojtek Lerch

unread,
Mar 16, 2013, 2:47:08 PM3/16/13
to
On 16/03/2013 10:10 AM, Shao Miller wrote:
> How many levels of indirection should such a convenience be willing to
> travel?

I don't think it should apply to anything other than pointers to structs
or unions. Basically, if either x->y or x.y is valid today, then the
other one becomes valid too, with the same meaning.

Keith Thompson

unread,
Mar 16, 2013, 3:47:36 PM3/16/13
to
Shao Miller <sha0....@gmail.com> writes:
> On 3/16/2013 07:21, JSmith wrote:
>> On Mar 15, 10:49 pm, Tim Rentsch <t...@alumni.caltech.edu> wrote:
[...]
>>> In modern C, because type information is available the distinction
>>> becomes redundant. Indeed there is an analogous case where this
>>> kind of redundancy has been exploited, namely in calling through
>>> a pointer to function, eg, int (*fp)( int, int ), which can be
>>> used in an expression just like an ordinary function:
>>>
>>> blah = fp( 1, 2 );
>>>
>>> rather than
>>>
>>> blah = (*fp)( 1, 2 );
>>>
>>> Clearly a similar kind of transformation could be made with -> and .
>>
>> Exactly. This is a relic of the very earliest versions of C. and is
>> unnecessary. Essentially all languages that are derived from the C
>> syntax have discarded it. I (and many others) have suggested for years
>> that this be changed, but it has never become a priority for the
>> committee.
>
> How many levels of indirection should such a convenience be willing to
> travel?

IMHO, the only sensible answers are 0, 1, and infinity (0 being
the current rule); anything else would be too arbitrary.

I'd vote for 1, so that `foo.bar` would be valid if `foo` is either
a struct or union or a pointer to a struct or union, but not if
`foo` is a pointer to pointer.

On the other hand, I don't greatly mind leaving it at 0. Introducing
`ptr.member` in a new revision of the language would encourage
writing code that can't be compiled with older compilers, even
though it doesn't really add any expressive power. If it were
introduced, I'd advocate taking advantage of it only in new code
that inherently depends on the new standard for other reasons.
(It should be easy enough to implement in compilers that I'd expect
it to be one of the first new features implemented.)

The publication of K&R1 in 1978, or the first ANSI standard in
1989, would have been good opportunities to introduce this feature.
But now, I think that ship has sailed.

[...]

Shao Miller

unread,
Mar 16, 2013, 4:32:59 PM3/16/13
to
Agreed.

struct { int x; double d; } arr[3][4];
arr[0].x = arr[0][1].x = 42;

Richard Damon

unread,
Mar 16, 2013, 10:56:48 PM3/16/13
to
C++ allows a struct to define an operator ->() that returns a pointer to
a struct (or another struct which defines an operator ->()) that
ultimately points to a structure with a member names named after the ->
operator.

This says that to remain compatible, C can not now define x->y to be the
same as x.y when x is a struct.

Since C++ does not allow overloading the . operator, defining x.y for x
being a pointer to struct type, to be the same as x->y could be
acceptable. This would also sort of compare to not needing the * in
front of pointer to functions inorder to call them.

x.y would effectively map into (*x).y for x being a pointer.

Joe keane

unread,
Mar 17, 2013, 4:05:24 PM3/17/13
to
In article <ki1ud0$s6p$1...@dont-email.me>,
Shao Miller <sha0....@gmail.com> wrote:
>How many levels of indirection should such a convenience be willing to
>travel?

a lot

#include <stdio.h>

#define NST 11000


int main(int argc, char **argv)
{
int j;

fputs("#include \"string.h\"\n", stdout);
fputs("int mystrcmp(char *x, char *y)\n", stdout);
fputs("{\n", stdout);
fputs(" return (", stdout);
for (j = 0; j < NST; j++)
fputc('*', stdout);
fputs("strcmp)(x, y);\n", stdout);
fputs("}\n", stdout);
return 0;
}

Owen Shepherd

unread,
Mar 18, 2013, 12:24:26 PM3/18/13
to

Richard Damon wrote:
> Since C++ does not allow overloading the . operator, defining x.y for x
> being a pointer to struct type, to be the same as x->y could be
> acceptable. This would also sort of compare to not needing the * in
> front of pointer to functions inorder to call them.

C++ is unlikely to make this change, because it would make smart
pointers no longer isomorphic with regular pointers.


Miles Bader

unread,
Mar 19, 2013, 2:13:58 AM3/19/13
to
Owen Shepherd <nn...@owenshepherd.net> writes:
> C++ is unlikely to make this change, because it would make smart
> pointers no longer isomorphic with regular pointers.

Why would it do that? "." with a pointer could invoke operator->...

-miles

--
Pray, v. To ask that the laws of the universe be annulled in behalf of a
single petitioner confessedly unworthy.

Bart van Ingen Schenau

unread,
Mar 19, 2013, 5:42:50 AM3/19/13
to
On Tue, 19 Mar 2013 15:13:58 +0900, Miles Bader wrote:

> Owen Shepherd <nn...@owenshepherd.net> writes:
>> C++ is unlikely to make this change, because it would make smart
>> pointers no longer isomorphic with regular pointers.
>
> Why would it do that? "." with a pointer could invoke operator->...

Because smart pointers in C++ can (and typically do) have additional
members besides operator-> and operator* for accessing the pointed-to
object.

Making operator. auto-dereference would make code like this
class X { /*...*/ };
std::auto_ptr<X> p;
//...
p.reset(); // does it work on p or on *p?
at best ambiguous and at worst a change in semantics.

>
> -miles

Bart v Ingen Schenau

lawrenc...@siemens.com

unread,
Mar 22, 2013, 2:46:47 PM3/22/13
to
Keith Thompson <ks...@mib.org> wrote:
>
> The publication of K&R1 in 1978, or the first ANSI standard in
> 1989, would have been good opportunities to introduce this feature.
> But now, I think that ship has sailed.

We actually had a long discussion over lunch one day about the idea,
back in the day. The consensus was that there was still value in
maintaining the semantic distinction between the two operators. That
discussion, however, predated the decision to have function types
automatically convert to points the way arrays do and have all function
calls made with pointers. If we were to have the discussion today, we
might very well decide to make . work both ways (although I'm not so
sure about ->).
--
Larry Jones

It doesn't have a moral, does it? I hate being told how to live my life.
-- Calvin

Seebs

unread,
Apr 25, 2013, 7:47:49 PM4/25/13
to
On 2013-03-22, lawrenc...@siemens.com <lawrenc...@siemens.com> wrote:
> Keith Thompson <ks...@mib.org> wrote:
>> The publication of K&R1 in 1978, or the first ANSI standard in
>> 1989, would have been good opportunities to introduce this feature.
>> But now, I think that ship has sailed.

> We actually had a long discussion over lunch one day about the idea,
> back in the day. The consensus was that there was still value in
> maintaining the semantic distinction between the two operators. That
> discussion, however, predated the decision to have function types
> automatically convert to points the way arrays do and have all function
> calls made with pointers. If we were to have the discussion today, we
> might very well decide to make . work both ways (although I'm not so
> sure about ->).

Having waffled back and forth on this, overall I think I prefer having the
semantic distinction exist, just because 90% of the time, if I get an error
because I picked the wrong operator, it means I have an incorrect cognitive
model of what I'm doing.

Antoine Leca

unread,
Apr 26, 2013, 7:07:31 AM4/26/13
to
Keith Thompson wrote:
> Seebs <usenet...@seebs.net> writes:
>> Imagine a language otherwise like C, in which the "." operator can
>> take either a struct or a pointer-to-struct, and does the same thing
>> either way[...]
>
> One data point: Ada does exactly that. [...]
> and I'm not aware that it's caused any problems.

... if you except they needed to introduce a keyword (all) only because
of that confusion: .all is used when you want to denote the pointed-to
object rather than the pointer (I know in C this is not a problem since
there is the unary * operator which does not exist in Ada.)


Antoine

Keith Thompson

unread,
Apr 26, 2013, 1:40:58 PM4/26/13
to
To put it another way, C spells its deference operator "*" (prefix),
and Ada spells it ".all" (postfix).

christ...@cbau.wanadoo.co.uk

unread,
Nov 5, 2013, 7:19:03 PM11/5/13
to
In Objective-C, which is a widely-used superset of C, there are certain situations where both "." and "->" would be legal with quite different meaning.

Seebs

unread,
Nov 5, 2013, 7:23:50 PM11/5/13
to
Oh, interesting! I was not aware of that.

-s
--
Copyright 2013, all wrongs reversed. Peter Seebach / usenet...@seebs.net
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
Autism Speaks does not speak for me. http://autisticadvocacy.org/
0 new messages