Justification for "->"?

K

Keith Thompson

No, but that is because - at least in theory, although in C it requires
a cast, and is one of the few contexts which IMO _should_ require one -
assigning an int to a pointer has a meaning. It's a non-portable,
implementation-defined meaning, but it _does_ have a meaning.

Actually, in standard C 'ptr = some_value;' is a constraint violation,
so it doesn't have an unambiguous meaning. Some implementations do
allow it in some modes (for compatibility with pre-ANSI C), and as far
as I know all of them do an implicit conversion. But an
implementation could conceivably support 'ptr = some_value;' (where
some_value has the type of *ptr) as an extension with some different
semantics.

But allowing 'ptr = some_value;' to mean '*ptr = some_value;' would
cause other problems; it's already well-defined if ptr is of type
void** and some_value is of type void*.
OTOH,

ptr.member = value;

has only one conceivable meaning. It can only reasonably mean the same
thing as

(*ptr).member = value;

alias

ptr->member = value;

Agreed. (Well, I could certainly conceive of other meanings, but I
can't think of any other *sensible* meaning.)

[...]
Seen in this light,

ptr.member = value;

is less similar to

ptr = int_value;

and more to

function_ptr();

which should, in theory, be written

*function_ptr();

but for which we have also been given leave to eliminate the explicit
dereference, because there's no other way it can be interpreted anyway.

Except that the reason 'function_ptr()' works is that the
function-call "operator" requires a function pointer as its first
operand. In a sense, 'function_name()' (calling a function using the
function's name directly) is the odd case, as it requires an implicit
conversion of the function's name to a pointer value. Without that
implicit conversion, you'd have to write '(&function_name)()'.

Now if I were designing the language from scratch, I might just drop
the implicit conversion of a function name to a function pointer and
make the function-call "operator" require an actual function. This
would require 'function_name()' for direct calls and
'(*function_ptr)()' for indirect calls. Of course this would break
existing code, so I wouldn't advocate making such a change now.
IMO, the same reasoning which gave us dereferenceless function pointer
calling - and that, /nota bene/, newly introduced by the first Standard,
because TTBOMK it was not allowed in K&R - could, and perhaps should,
give us dereferenceless struct and union pointer member taking.

I'll have to check my copy of K&R1.

I've given my reasons for not wanting to add 'ptr.member' elsethread.
 
U

user923005

A quick search of this group and its FAQ, and elsewhere, have not
answered this question to my satisfaction. Apologies if I missed
something obvious, either in the literature or my reasoning.

Can someone tell me why "->" exists? The compiler knows the difference
between a structure and a pointer to a structure, so why can't it just
let me write "foo.bar" in both cases and not have to go back and
rewrite things when I later decide I want a pointer instead of a value
or vice versa? I often change my mind about whether e.g. a function
should receive a pointer or the thing itself, and it's mildly
inconvenient to have to change all the code that references the
members.

Is there a historical reason? I read that dmr article on the history
of C, and he mentions a time when "foo->bar" would work regardless of
the type of "foo", with "bar" just describing an offset and the type
of the member value, but this confuses me -- was one not allowed to
use the same member name in two different structure types? But even if
the original reason was related to this, why was the division kept
after the type system got stronger?

It's one character less to get the member for:
foo->bar
than:
*(foo).bar

C programmers are incredibly lazy. It's like ls. It is one character
less than dir.

As far as your separation, I *intensely* dislike the idea of:
foo.bar
when I mean:
foo->bar

That's because I'm stupid. It's also why I hate references in C++.
Allow me to explain...

If I see:
foo->bar = frabazz;
Then I know I am dealing with a pointer. If I modify a pointer, then
it can change the value in the calling routine. So if this thing is a
parameter, I need to worry about what happens up above. If (on the
other hand) I see this:
foo.bar = frabazz;
Then I know for sure I only have to worry about it in this function.
It's not hard for me to think about what happens within the space of
50-70 lines. In fact, I can see it right in front of me. But if I
have to chase it all over God's green earth, it becomes a lot more
problematic. Hence, for me, '->' is a warning and '.' is a green
light.

IMO-YMMV.
 
K

Keith Thompson

Keith Thompson said:
(e-mail address removed) (Richard Bos) writes: [...]
Seen in this light,

ptr.member = value;

is less similar to

ptr = int_value;

and more to

function_ptr();

which should, in theory, be written

*function_ptr();

but for which we have also been given leave to eliminate the explicit
dereference, because there's no other way it can be interpreted anyway.

Except that the reason 'function_ptr()' works is that the
function-call "operator" requires a function pointer as its first
operand. In a sense, 'function_name()' (calling a function using the
function's name directly) is the odd case, as it requires an implicit
conversion of the function's name to a pointer value. Without that
implicit conversion, you'd have to write '(&function_name)()'.

Now if I were designing the language from scratch, I might just drop
the implicit conversion of a function name to a function pointer and
make the function-call "operator" require an actual function. This
would require 'function_name()' for direct calls and
'(*function_ptr)()' for indirect calls. Of course this would break
existing code, so I wouldn't advocate making such a change now.
IMO, the same reasoning which gave us dereferenceless function pointer
calling - and that, /nota bene/, newly introduced by the first Standard,
because TTBOMK it was not allowed in K&R - could, and perhaps should,
give us dereferenceless struct and union pointer member taking.

I'll have to check my copy of K&R1.
[...]

Ok, I've just checked K&R1, and you're right. In K&R1 Appendix A,
section 7.1:

A function call is a primary expression followed by parentheses
containing a possibly empty, comma-separated list of expressions
which constitute the actual arguments to the function. The
primary expression must be of type "function returning ...", and
the result of the function call is of type "...".

But it does have the rule about function names being implicitly
converted to function pointers:

Likewise, an identifier which is declared "function returning
...", when used except in the function-name position of a call, is
converted to "pointer to function returning ...".

C89/C90 changed the rules to enable the conversion even in a function
call context, and to make a function call require a
pointer-to-function rather than a function name. (I think I like the
K&R rule better, since it didn't allow silly things like
'******func()'.)
 
K

Kenny McCormack

user923005 said:
It's one character less to get the member for:
foo->bar
than:
*(foo).bar

C programmers are incredibly lazy. It's like ls. It is one character
less than dir.

I think the real reason for having -> instead of *(foo) is to avoid the
problem of people forgetting the parens and writing: *foo.bar and not getting
what they expect. IOW, you shouldn't need parens for common everyday
operations. The presence of parens should indicate that something out
of the ordinary is intended.

Analogy: We all know that: a * b + c
means multiply a*b, then add c. And it is this way because most of the
time, that's what you want (because that's the way the underlying
algebra works). But it could certainly have been otherwise.

Suppose that most of the time, you really wanted: a*(b+c). If that had
been the case, then the rules would have been setup to honor that. Now,
suppose that 55% of the time, you want (a*b)+c, and that 45% of the time
you want a*(b+c). In that case, they'd have provided an operator or
function to do that, rather than make you write out the parens 45% of
the time.
 
P

pete

Richard said:
user923005 said:



Er, so what? They mean different things.

ITYM (*foo).bar

It's extra for me because I place spaces around -> operators,
but not around.operators.

I think it's because the top half of short (not tall) characters,
makes the characters look spacier to me.
 
R

Richard Bos

Keith Thompson said:
Actually, in standard C 'ptr = some_value;' is a constraint violation,
so it doesn't have an unambiguous meaning. Some implementations do
allow it in some modes (for compatibility with pre-ANSI C), and as far
as I know all of them do an implicit conversion. But an
implementation could conceivably support 'ptr = some_value;' (where
some_value has the type of *ptr) as an extension with some different
semantics.

Erm... isn't that what I said? It needs a cast, but when it gets it, it
has a meaning - but a system-specific meaning.
But allowing 'ptr = some_value;' to mean '*ptr = some_value;' would
cause other problems; it's already well-defined if ptr is of type
void** and some_value is of type void*.

True.

Richard
 
K

Keith Thompson

Erm... isn't that what I said? It needs a cast, but when it gets it, it
has a meaning - but a system-specific meaning.

Yes, I see that that's what you meant, but your statement that
"assigning an int to a pointer has a meaning" implies that assigning
*without a cast* has a meaning. In fact, assigning an int to a
pointer has no defined meaning; if you add a cast, you're simply
assigning a pointer to a pointer (and the cast has a non-portable,
implementation-defined meaning).

[snip]

And in my opinion the constraint in C99 6.5.4p3:

Conversions that involve pointers, other than where permitted by
the constraints of 6.5.16.1, shall be specified by means of an
explicit cast.

is redundant, and should be a note rather than a constraint. The fact
that no implicit conversions are defined for pointer types is
sufficient. Strictly speaking, you can't even *try* to do an implicit
pointer conversion, so there's no way for a program to violate the
constraint.
 
R

Richard Bos

Keith Thompson said:
Keith Thompson said:
(e-mail address removed) (Richard Bos) writes:
[...]
We don't write:

ptr = some_value;

No, but that is because - at least in theory, although in C it requires
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Erm... isn't that what I said? It needs a cast, but when it gets it, it
has a meaning - but a system-specific meaning.

Yes, I see that that's what you meant, but your statement that
"assigning an int to a pointer has a meaning" implies that assigning
*without a cast* has a meaning.

Erm... *looks up* are you sure?

Richard
 
K

Keith Thompson

Keith Thompson said:
(e-mail address removed) (Richard Bos) writes:
[...]
We don't write:

ptr = some_value;

No, but that is because - at least in theory, although in C it requires
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
a cast, and is one of the few contexts which IMO _should_ require one -
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
assigning an int to a pointer has a meaning. It's a non-portable,
implementation-defined meaning, but it _does_ have a meaning.

Actually, in standard C 'ptr = some_value;' is a constraint violation,
so it doesn't have an unambiguous meaning. Some implementations do
allow it in some modes (for compatibility with pre-ANSI C), and as far
as I know all of them do an implicit conversion. But an
implementation could conceivably support 'ptr = some_value;' (where
some_value has the type of *ptr) as an extension with some different
semantics.

Erm... isn't that what I said? It needs a cast, but when it gets it, it
has a meaning - but a system-specific meaning.

Yes, I see that that's what you meant, but your statement that
"assigning an int to a pointer has a meaning" implies that assigning
*without a cast* has a meaning.

Erm... *looks up* are you sure?

Richard

Yes, I'm sure. Assigning an int to a pointer (other than the special
case of a null pointer constant) has no meaning in C; it's a
constraint violation. Converting an int to a pointer type with a cast
and assigning the result to a pointer is not "assigning an int to a
pointer"; it's assigning a pointer to a pointer. The fact that the
pointer being assigned was formed by converting an int value doesn't
change that.

The conversion itself has a system-specific meaning, as you say. That
meaning has nothing to do with whether the result happens to be
assigned to a pointer object.

You can no more assign an int to a pointer (OTTSCOANPC) than you can
pass an array as a function argument.
 
R

Richard Bos

Keith Thompson said:
Keith Thompson said:
(e-mail address removed) (Richard Bos) writes:
(e-mail address removed) (Richard Bos) writes:
[...]
We don't write:

ptr = some_value;

No, but that is because - at least in theory, although in C it requires
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

a cast, and is one of the few contexts which IMO _should_ require one -
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

assigning an int to a pointer has a meaning. It's a non-portable,
implementation-defined meaning, but it _does_ have a meaning.

Actually, in standard C 'ptr = some_value;' is a constraint violation,
so it doesn't have an unambiguous meaning. Some implementations do
allow it in some modes (for compatibility with pre-ANSI C), and as far
as I know all of them do an implicit conversion. But an
implementation could conceivably support 'ptr = some_value;' (where
some_value has the type of *ptr) as an extension with some different
semantics.

Erm... isn't that what I said? It needs a cast, but when it gets it, it
has a meaning - but a system-specific meaning.

Yes, I see that that's what you meant, but your statement that
"assigning an int to a pointer has a meaning" implies that assigning
*without a cast* has a meaning.

Erm... *looks up* are you sure?

Yes, I'm sure.

Then I humbly submit that you have not read the whole of my statement;
in particular, that you haven't read the side remark between hyphens,
which is an integral part of it.

Richard
 
K

Keith Thompson

Keith Thompson said:
(e-mail address removed) (Richard Bos) writes:
(e-mail address removed) (Richard Bos) writes:
[...]
We don't write:

ptr = some_value;

No, but that is because - at least in theory, although in C
it requires

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

a cast, and is one of the few contexts which IMO _should_
require one -

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

assigning an int to a pointer has a meaning. It's a
non-portable, implementation-defined meaning, but it _does_
have a meaning.

Actually, in standard C 'ptr = some_value;' is a constraint
violation, so it doesn't have an unambiguous meaning. Some
implementations do allow it in some modes (for compatibility
with pre-ANSI C), and as far as I know all of them do an
implicit conversion. But an implementation could conceivably
support 'ptr = some_value;' (where some_value has the type of
*ptr) as an extension with some different semantics.

Erm... isn't that what I said? It needs a cast, but when it
gets it, it has a meaning - but a system-specific meaning.

Yes, I see that that's what you meant, but your statement that
"assigning an int to a pointer has a meaning" implies that
assigning *without a cast* has a meaning.

Erm... *looks up* are you sure?

Yes, I'm sure.

Then I humbly submit that you have not read the whole of my statement;
in particular, that you haven't read the side remark between hyphens,
which is an integral part of it.

I think that we're quibbling about wording here; we both understand
how this stuff works, but we're disagreeing about how to express it.
But IHHO it's an important distinction, so I'll try one more time.

Assigning an int to a pointer does not have a meaning; it's a
constraint violation. Adding a cast does not make it possible to
assign an int to a pointer; it merely makes it possible to assign a
pointer to a pointer.

For example, given:

int i;
int *p;
/* ... */
p = i; /* constraint violation */

the assignment is illegal. Adding a cast
p = (int*)i;
is merely one way to make it legal, but it makes it a different
statement. Other ways to make it legal are:
p = &i;
*p = i;
or even:
/* p = i; */
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,121
Latest member
LowellMcGu
Top