Topic: const should be a prefix operator
Author: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1995/04/28 Raw View
In article <FENSTER.95Apr26171828@ground.cs.columbia.edu>
fenster@ground.cs.columbia.edu (Sam Fenster) writes:
|> kanze@us-es.sel.de (James Kanze US/ESC 60/3/141 #40763) writes:
|> > fenster@ground.cs.columbia.edu (Sam Fenster) writes:
|> >> Is there any reason that the grammar was designed to allow the first two
|> >> statements below, but not the third?
|> >> int const *pci; //Legal
|> >> int j, *const cpj = &j, *const *const cpcpj = &cpj; //Legal
|> >> int k, const *pck; //Parse error!
|> > What is `const' in the third one, the comma?
|> No, laughing boy, *pck is const. It's int const. Why don't you apply your
|> witty sarcasm to `cpj' above and ask, "What is `const' in the second one, a
|> pointer to the comma?"
|> See if you detect a pattern here:
|> int **const p; // p cannot be changed, and is of type (int **)
|> int *const *p; // *p cannot be changed, and is of type (int *)
|> int const **p; // **p cannot be changed, and is of type (int)
|> int *const (a[10]); // a[i] cannot be changed, and is of type (int *)
|> int (*const pa)[10]; // pa cannot be changed, and is of type (int (*)[10])
Looks OK so far. This is *not* the traditional way of viewing it, but
in the above declarations, works (superficially, at least).
The problem is that you are considering `p' as part of the type
expression. Try applying the same rules to a type in a cast, or in a
new expression, or a sizeof:
static_cast< int **const >
new int *const*
sizeof( int (*const)[ 10 ] )
How would you formulate these? (Note that correctly parsing the type
expression in a new expression is important in order to know what type
of delete to use. Your formulation does not seem to help here.)
Const is part of the type. One can consider `const' as a sort of an
adjective, whereas the `*' (in type expressions!) is a noun. In all
cases, the `const' *modifies* the noun to the left of it (with the
exception of "const int").
|> Do you see how `const' operates on the type of the expression that follows?
|> Do you see how it is like `*' in this respect? (Do you see that `*' is a
|> prefix operator?)
Do you see that `const' is *not* an operator? Do you see how const
modifies the type to the left of it, e.g.:
int *const -> const(ant) pointer to int
int const* -> pointer to const(ant) int
Do you see how dealing in terms of type expressions (as is done in
chapter 8 of the ARM, in the C standard, and no doubt, in the upcoming
C++ standard) works in every case (even when no variable is being
declared)?
|> Syntactically, `const' is a prefix operator (with an
|> annoying exception, which was the point of my post, and which I'll get to
|> below).
Not in the current working papers, nor in the ARM nor the C standard.
See section 8.3.1 (Pointers) in the current working papers:
"[concerning * cv-qualifier-seq[opt] D1...] The cv-qualifiers apply to
the pointer and not to the object pointed to."
You say one thing. The current working papers, the ARM and the C
standard say another. Who am I to believe?
|> The semantics of the `const' prefix operator, roughly, are that an
|> expression that looks like its operand cannot be modified.
The semantics of the `const' qualifier, exactly, are that the part of
the *type* to which it applies becomes `const qualified'. (In C, this
does not define a new type, in C++, it does.) No more, no less. The
semantics of `const qualified' depend on where it occurs in the type
in question. If the top level type of an object is const qualified,
the object is, in standardese, "non-modifiable". (Strictly speaking,
this is not always true for objects of class type. And of course,
there are objects whose type is not const-qualifiable which are also
non-modifiable.)
Within an expression, an object is deamed non-modifiable if its top
level type is const qualified. This actually leads to a curious
paradox:
char* p1 = "abcd" ; // *p1 non-modifiable, but not const
char c ;
char const* p2 = &c ; // *p2 modifiable, but const
*p1 = '0' ; // syntactically legal, but
// undefined behavior. In practice,
// no compiler error.
*p2 = '0' ; // compiler error, although were
// it legal, well defined.
|> The larger type
|> expression that contains `const' and its operand declares the type of that
|> entire const operand.
|> int *****p; // Can be seen as declaring (**p) to be (int***).
Can be seen as declaring p as: a pointer to a pointer to a pointer to
a pointer to a pointer to an int.
|> int ***const **p; // Can be seen as declaring (**p) to be (int ***const).
|> // or, equivalently, as declaring (**p), which is const, to be (int ***).
Can be seen as declaring p as: a pointer to a pointer to a const
pointer to a pointer to a pointer to an int.
|> You (James) seem to view `const' as a _postfix_ operator by saying that in
|> int ***const **p;
|> `const' applies to `int ***'. That seems to work OK. But it fails with
|> anything complex:
|> int *(const *pb)[10]; // Same as: int *((const *pb)[10]);
Completely illegal. The above cannot be parsed according to the C/C++
grammar. No compiler should accept it.
|> int (*const *pa)[10];
|> `const' doesn't apply to `int (*' here, it applies to `int (*)[10]', making
|> `*pa' be of type `int (*const)[10]'.
Const applies to a pointer (to an array[10] of int).
Const qualifies (modifies) a type, not an object. An object whose top
level type is const *may* be non-modifiable. (I say may: according to
the rules in the ARM, which have been modified in the working papers,
most of my objects with const type *are* modifiable, since they are
classes with constructors and destructors. All the const means is
that the unqualified name of the object will be viewed as
non-modifiable if it appears in an expression.)
|> In other words, it "applies" the type
|> defined by all the later type operators (those of lower precedence in the
|> declaration -- those outside the `(const ...)' in a complete parenthesization
|> of the declaration). It "applies" that outer type "to" the earlier expression
|> (inside the `const (...)'), which is `*pa'.
|> Just like in
|> cos ((3 * -(x*x)) * 10)
|> we could, with this same viewpoint, say the unary minus `-' was being "applied
|> to" `cos ((3 * ?) * 10)', making it `cos ((3 * -?) * 10)', rather than saying
|> that `-' applies to `(x*x)'. But syntactically, it's glued to `(x*x)'.
|> That's a prefix operator, no matter which way you look at it. `const' applies
|> to `*pa' in exactly the same way.
No. Not according to the C standard, the ARM, and the current working
papers. Unary '-' is defined as a prefix operator, explicitly. The
current working papers say that in the type declarator "*cv-qualifiers D",
the cv-qualifiers (const, volatile) apply to the pointer. See the
text quoted above.
|> Now here's the problem I was talking about in the post you replied to:
|> int **const p; // p is (int **const)
|> int *const *p; // p is (int *const *)
|> int const **p; // p is (int const **)
|> int i, **const p; // p is (int **const)
|> int i, *const *p; // p is (int *const *)
|> int i, const **p; // Illegal!? p should be (int const **)
|> Get it? The grammar doesn't allow the last one, but it should. `const' makes
|> perfect sense there.
The grammar doesn't allow the last one because it doesn't make sense.
OK, I think I see what you are getting at. "int i, const **p" is
(should be) exactly equivalent to:
int i ;
int const **p ;
Since this is legal, and well defined, so should be comma form be.
All I can say is that the illegality of "int i , const..." occurs
naturally from the way C/C++ declarations are defined. Given that
const defines a real type in C++, it would be fairly simple to change
the grammar to allow what you want. Of course, this change would
break "const int *p". This wouldn't cause me any problems, but I
rather suspect that there are number of people who would be less than
happy about it:-).
Of course, the simplest (and probably the best) solution is just to
declare only one variable per declaration. Again, there is enough
legacy code out there that I don't think making this mandatory in the
standard would be a good idea.
|> >> I guess this one
|> >> int const *pcl, *pm;
|> >> presents a problem, backward-compatibilitywise. If `const' were a prefix
|> >> operator like `*' in declarations, the way I'd like it to be, then pm would
|> >> be `int *'. But under the current rules, pm is `int const *'.
|> >
|> > Again, you are not making sense.
|> Compare that declaration to
|> int *const *pcp, **pp;
|> where pp is `int **', because `const' is a prefix operator that only operates
|> on the expression `*pcp'. By contrast,
|> int const *pcl, *pm;
|> makes `*pm' be const. I'm saying that changing the grammar to the way I would
|> like it would break this code. That's the only reason for not making the
|> change.
The main reason for not making the change is that C/C++ declarations
are fundamentally divided into two parts: the declaration specifier
and the declarator (list). It is this division into parts that allows
you to declare several variables in one declaration (because it allows
a declarator list, instead of just a single declarator). (It also
accounts for the fact that `const' is position independant in the
declaration specifier, but postfix in a declarator.)
Because `const' defines a new type in C++, one could conceive of
making it a true type modifier (like `*') and moving it into the
declarator in all cases. As this would make "const int" illegal, I
wouldn't bet on its happening.
|> Of course,
|> const int *pcl, *pcm;
|> should make *pcl and *pcm both be const. This is intuitive. Modifiers of the
|> basic type (`int') that come before it should (like `int') apply to all
|> identifiers in the declaration list.
Qualifiers in the declaration specifier qualify the basic type.
|> > Const is not an operator, but a type qualifier.
|> I didn't mean it was a C "operator". I meant that mathematically, it is
|> applied as a prefix to a symbolic expression with a meaning (not a C
|> "expression"), yielding another, larger symbolic expression with a related
|> meaning, which can in turn be combined with more operators.
I understand. Except that according to the ARM, the C standard and
the working papers, in a declarator, it is applied to the *preceding*
token, qualifying the *type*.
|> > In the declaration specifier, it modifies the basic type (which is the only
|> > type defined in the declaration specifier); otherwise, it modifies the
|> > element to its *left* (which is why your third example doesn't make sense;
|> > there is no element to the left of const).
|> I think I've made it very clear that it makes sense.
|> > If const were an operator, it would be a postfix operator.
|> Syntactically? Nope:
|> > For this reason, I like the style of always putting const after what it
|> > modifies; this retains the basic right to left readings of C++
|> > declarations: [...]
|> > (Note that this rule does not work in the presence of multiple dimension
|> > arrays; the array specifiers must be read from right to left.)
|> Yes, the rule that works is not "right-to-left", but "innermost-to-outermost".
There is *no* simple rule that works. Innermost to outermost is the
classical spiral rule, which is constantly being suggested, but fails
for such expressions as int **a[5][3].
There *is* no simple rule. You *must* deal in terms of type
expression, using the precedences defined by the grammar. *If* you
ignore the actual dimensions of the array, right to left will seem to
work, but only because 1) by ignoring the dimensions, you cannot
detect the mismatch of the arrays, and 2) you cannot have a function
which returns an array, nor an array of functions.
Author: kanze@us-es.sel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1995/04/26 Raw View
In article <FENSTER.95Apr24233252@ground.cs.columbia.edu>
fenster@ground.cs.columbia.edu (Sam Fenster) writes:
|> Is there any reason that the grammar was designed to allow the first two
|> statements below, but not the third?
Yes, the third one doesn't make any sense.
|> It's an annoying nonorthogonality:
|> int const *pci; //Legal
|> int j, *const cpj = &j, *const *const cpcpj = &cpj; //Legal
|> int k, const *pck; //Parse error!
|> Can this be fixed in the standard?
I don't see what is to be fixed. What is `const' in the third one,
the comma?
|> I guess this one
|> int const *pcl, *pm;
|> presents a problem, backward-compatibilitywise. If `const' were a prefix
|> operator like `*' in declarations, the way I'd like it to be, then pm would be
|> `int *'. But under the current rules, pm is `int const *'.
Again, you are not making sense. You seem to be complaining the `int
const*' means `int const*'.
Const is not an operator, but a type qualifier. In the declaration
specifier, it modifies the basic type (which is the only type defined
in the declaration specifier); otherwise, it modifies the element to
its *left* (which is why your third example doesn't make sense; there
is no element to the left of const). If const were an operator, it
would be a postfix operator.
For this reason, I like the style of always putting const after what
it modifies; this retains the basic right to left readings of C++
declarations:
int const * p [10] ;
// From right to left: p is a:
// [10] array[10] of
// * pointer to
// const const
// int int
int * const p [10] ;
// p is a:
// [10] array[10] of
// const const
// * pointer to
// int int
(Note that this rule does not work in the presence of multiple
dimension arrays; the array specifiers must be read from right to
left.)
|> So in declarations, `const' is sometimes a prefix operator; but other times
|> it's a specifier (like `int') that can only appear at the start of the
|> declaration and applies to every name being declared in it. This is really
|> ugly. Looks like a mistake in the language design.
Const is never an operator. Const is never a `specifier' (whatever
that is). Const is a qualifier, which in most cases, qualifies
whatever is to its left.
--
James Kanze Tel.: (+33) 88 14 49 00 email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung
Author: fenster@ground.cs.columbia.edu (Sam Fenster)
Date: 1995/04/25 Raw View
Is there any reason that the grammar was designed to allow the first two
statements below, but not the third? It's an annoying nonorthogonality:
int const *pci; //Legal
int j, *const cpj = &j, *const *const cpcpj = &cpj; //Legal
int k, const *pck; //Parse error!
Can this be fixed in the standard?
I guess this one
int const *pcl, *pm;
presents a problem, backward-compatibilitywise. If `const' were a prefix
operator like `*' in declarations, the way I'd like it to be, then pm would be
`int *'. But under the current rules, pm is `int const *'.
So in declarations, `const' is sometimes a prefix operator; but other times
it's a specifier (like `int') that can only appear at the start of the
declaration and applies to every name being declared in it. This is really
ugly. Looks like a mistake in the language design.