|
Boost : |
From: danl_miller (danl_miller_at_[hidden])
Date: 2002-02-19 13:06:39
[ auto IS ALREADY A KEYWORD ]
--- In boost_at_y..., Daniel Frey <daniel.frey_at_a...> wrote:
> ...
> Why not think of a better solution? It is just me feeling that there
> *is* a better solution? I don't like to give up so soon and accept
> 'auto' as a keyword.
> ...
--- In boost_at_y..., Daniel Frey <daniel.frey_at_a...> wrote:
> ...
> If finally 'auto' as a keyword is the only and best option to solve
> the problems, OK, but it shouldn't be added just because it's easy.
> ...
Note that auto is already a keyword in C & C++. The keyword is just
rarely used. The presence of auto as an existing keyword is mentioned
by David Abrahams. (I wanted to call this fact out more directly.)
--- In boost_at_y..., "David Abrahams" <david.abrahams_at_r...> wrote:
> The reason to re-use auto is that it's "underloaded" (Andy Sawyer's
> term). Nobody uses it! As long as we have to have the keyword, it
> might as well be useful for something.
[ POTENTIAL FOR UNINTENDED CONSEQUENCES OF RE-USE OF auto ]
Because auto is already a keyword in C & C++, I would like to call
us all to caution not to break compatibility with C. I do not have an
important smoking-gun counter-example (yet), but we should all be on
the vigilant looking for unintended consequences of such an expansion
of a keyword established long ago for a different purpose used in
nearly the same context (as a storage class specifier). Many prior
overloadings of keywords were in drastically different contexts (e.g.,
static at file scope versus static member-functions & static
member-data where by definition file scope and class/struct membership
are spaces/contexts which never overlap). Overloadings in
substantially the same context (i.e., *storage class* [existing =
stack variable] versus *storage class* [proposed = discover type from
initialization]) have a greater chance of wreaking unintended havoc.
Currently, I myself do not consider the difference between C's
auto foo;
to deduce foo to be an int versus the proposed C++
auto foo = "example";
to be a const char* as an important smoking-gun counter-example.
Likewise, I myself do not consider the difference between C's
acceptance of
auto foo;
versus C++'s rejection of [in the proposed implied-type declaration
usage of auto]
auto foo;
due to lack of initialization from which to deduce the implied type to
be an important smoking-gun counter example.
--- In boost_at_y..., "brianjparker" <brianjparker_at_h...> wrote:
> ...
> My major objection to the "auto" proposal is its syntax- I think
that
> one can achieve the same result as a pure extension without
> overloading a keyword (or introducing a new keyword) by simply
> extending to function return and local variables the existing type
> deduction rules applied to function arguments.
> e.g. in
>
> template<typename R, typename T1, typename T2>
> R min(T1 t1, T2 t2)
> {
> if (t1 < t2) return t1;
> else return t2;
> }
>
> if R is not explicitly specified in the function call then it is
> deduced (as if it were specified with the putative "auto" keyword.)
>
> This could also be extended to local variables-
>
> template<typename T1, typename T2, typename L>
> void func(T1 t1, T2 t2)
> {
> const L& l = t1 + t2;
> // ...
> }
>
> One possible disadvantage with this syntax is that it is limited to
> template functions by definition, but this in fact could be an
> advantage as it maintains the distinction between C-compatible non-
> template functions and C++ generic functions.
> ...
Note that this solution does not risk any conflict with any prior
usage of the keyword auto. That is an advantage. It becomes a big
advantage if at least one important smoking-gun counter-example is
found regarding this innovative re-use of the existing keyword auto.
[ AMBIGUOUS SYNTAX WITHOUT ANY STORAGE-CLASS SPECIFIER ]
--- In boost_at_y..., "Greg Colvin" <gcolvin_at_u...> wrote:
> And of course, having gotten rid of implicit int, we don't need a
> keyword at all:
>
> template<class T, class S>
> operator+(T &t, S &s) {
> v = t+s; // type of v is implicit
> return v; // return type is implicit
> }
>
> Or even easier:
>
> operator+(t, s) { // type of t and s are implicit
> v = t+s; // type of v is implicit
> return v; // return type is implicit
> }
This proposed syntax is far too subtle. The auto proposal & the
template typename proposal discussed beforehand had some obvious
explicit syntax which let human readers (and the compiler for that
matter) know that a new variable was being declared using the new
type-implied-by-initialization mechanism. This syntax has no
explicit indication that v is initialization of a new variable being
declared versus assignment to an existing variable declared at
file-scope (or more generally in other unmentioned examples,
initialization of a new variable at inner scope versus assignment to
an existing variable declared at outer scope or member-scope).
[ SHIFTING FROM ONLY auto TO ALL STORAGE CLASSES ]
--- In boost_at_y..., "mfdylan" <dylan_at_m...> wrote:
> ...
> In C this means the variable is of type int. In C++ it is
currently
> not allowed to omit the type, but it would a reasonable enhancement
> to allow this such that the type is deduced from the value you are
> initialising the variable with (of course if you don't provide an
> initialiser, auto cannot work). By extension you should also be
able
> to use register for local variables, and static/extern for global
> vars:
>
> static i = some_complex_function(); // no linkage
> extern j(some_other_function()); // linkage within current namespace
>
> void foo()
> {
> auto k = some_complex_function();
> register l(some_other_function());
> }
>
> Note that you can't use it for members, but seeing as you can't
> initialise members upon declaration (other than static const int)
> this isn't an issue.
>
> But if auto is to remain a storage class specifier you couldn't use
> it to declare functions. So either auto's meaning needs to be
> explicitly changed, or you would need to use extern or static
instead.
> I doubt too many (if any) people still use 'auto' these days, but
> there is a potential danger in taking an already well defined
keyword
> and changing it to mean something else.
> ...
M. F. Dylan has a very good point here which I as well implicitly
gravitated toward since this topic came up. Instead of focusing on a
complete hi-jacking & complete redinition of the keyword auto, in the
auto proposal let's focus on each storage-class retaining
its current C & C++ meaning of being purely a storage-class.
In the "auto proposal", let's reconsider it as the "storage class
proposal", where the focus is not on the auto keyword but on all of
the storage-class keywords: static, auto, register, and volatile.
The concept of a declaration becomes one of the following (in
simplified colloquial grammar):
1) storage-class type identifier and so forth (e.g., the existing
static int foo; and auto float bar;)
2) type identifier and so forth (e.g., the existing int foo; float
bar;)
3) storage-class identifier and so forth (e.g., the proposed auto
foo = "example"; but also static bar = 3.1415F)
The mental rule for using the proposed
type-implied-by-initialization language feature would be: omit the
type and ensure that a storage-class specifier is present. This fits
with existing initialization syntax at declaration-time (as opposed to
the unrelated topic of initialization of member-data via ctor
initialization list). Nearly everywhere initialization of a variable
or constant is permitted where a valid storage class specifier is
present:
1) at file scope: static
2) at member scope (constants): static
3) within a function/compound-statement: volatile, register, auto,
static
The only place that I can think of off the top of my head that
storage class specifier is not currently permitted where declaration
with initialization is permitted is: function arguments with default
value. If it is expected that one could use the proposed
type-implied-by-initialization language feature for function arguments
with default value, the auto storage class could be made permissible
(i.e., place the datum on the stack which is what is naturally implied
by storage class auto and by function arguments)
int foo( auto bar = "example" );
Conversely, function arguments with default value could simply not
participate in the proposed type-implied-by-initialization language
feature.
--- In boost_at_y..., "Emily Winch" <emily_at_b...> wrote:
> ...
> I think auto's meaning has to be explicitly overloaded anyway. What
> if you want to deduce the type of a static variable
>
> static auto foo = complicated_thing<x, y, z>::type;
>
> ? This is illegal at the moment even if we add a type ("static auto
> int....")
>
> However, as far as I can see (probably not that far :) ) using auto
> to declare functions doesn't invalidate or change the meaning of any
> existing correct syntax. Where's the danger?
> ...
Refocusing the attention away from redefining/embellishing the
semantics of the keyword auto (to no longer be the clean & simple
storage-class which it is today) and on omitting the type
leaving only a storage-class specifier, Emily Winch's example would
take the form:
static foo = complicated_thing<x, y, z>::type;
The syntax
static foo = complicated_thing<x, y, z>::type;
retains the long-standing and firmly-rooted concepts of the
storage-class specifiers---static, auto, register, and
volatile---without any form of recategorization or exceptional-case
handling in the human mind. The syntax with both static and auto
static auto foo = complicated_thing<x, y, z>::type;
significantly complicates the concept of storage-class in the
human-being's mind, requiring the human-being to file the information
as: "The storage classes in C++ are static, auto, register, and
volatile except that auto is weird in that it also is combinable with
other storage classes (i.e. the true storage classes) to have
non-storage-class behavior."
[ THE OTHER INITIALIZATION SYNTAX ]
I have noticed that all examples so far have been given in the
equal-sign = syntax of initialization. As a minor side-note, let's
not forget that initialization has another syntax using parentheses ()
evoking the concept of copy-ctor invocation.
auto foo = 6;
auto foo( 6 ); static foo = 7;
static foo( 7 );
auto frogWithWings = ::TheFrogWithWingsExemplar;
auto frogWithWings( ::TheFrogWithWingsExemplar );
Revisiting Greg Colvin's proposed syntax
--- In boost_at_y..., "Greg Colvin" <gcolvin_at_u...> wrote:
> And of course, having gotten rid of implicit int, we don't need a
> keyword at all:
>
> template<class T, class S>
> operator+(T &t, S &s) {
> v = t+s; // type of v is implicit
> return v; // return type is implicit
> }
>
> Or even easier:
>
> operator+(t, s) { // type of t and s are implicit
> v = t+s; // type of v is implicit
> return v; // return type is implicit
> }
these examples would appear as:
template<class T, class S>
operator+(T &t, S &s) {
v( t+s ); // type of v is implicit
return v; // return type is implicit
}
operator+(t, s) { // type of t and s are implicit
v( t+s ); // type of v is implicit
return v; // return type is implicit
}
which look like function calls instead of initialization of a
variable.
[ CONST OR NON-CONST? ]
Speaking "initialization of a *variable*", there appears to be a
potential ambiguity in certain uses of this
type-implied-by-initialization langauge feature: whether the datum
being declared is a variable/non-const or a constant/const.
Consider the following:
void fooConst()
{
const int m = 11;
auto c = i;
}
I suspect that most people would consider c to be a constant with
type const int as it would take on m's type.
Consider the following:
Consider the following:
void fooNonconst()
{
int n = 11;
auto v = i;
}
I suspect that most people would consider v to be a variable with
type int as it would take on n's type.
Consider the following:
void fooNonconstNonconst()
{
int i = 11;
int j = 3;
auto hmmm1 = i+j;
++hmmm1; // legal? or illegal?
}
Is hmmm1 a variable and thus modification of its value is legal? Or
is hmmm1 a constant and thus modification of its value is illegal?
Basing the implied type of hmmm1 on the types of i and j would allow a
human-being (and a compiler) to deduce that hmmm1 is int, where hmmm1
is a variable whose value can be changed. Basing the implied type of
hmmm1 on the fact that i+j is an expression could lead to a debate on
whether the expression should be treated more as a constant whose
value is 14 or more as a variable following the lead of the
declaration of i and j.
Consider the following:
void fooConstConst()
{
const int x = 12;
const int y = 4; auto hmmm2 = x+y;
++hmmm2; // legal? or illegal?}
I suspect that most people would consider hmmm2 to be constant
regardless of whether they focused on the declarations of x and y or
whether they focused on the expression.
Consider the following:
void fooConstNonconst()
{
const int p = 13;
int q = 5;
auto hmmm3 = p+q;
++hmmm3; // legal? or illegal?
}
I suspect that here the people who focus on the expression
evaluating to a constant value will tend to reach the conclusion that
hmmm3 is a constant with type const int. But I suspect that the
people who focus on the declaration of p and q will tend to enter a
debate on 1) whether to consider only p alone since it occurred first
in the expression versus 2) whether the dissimilarity between p's type
and q's type is causing additional contemplation of the matter (e.g.,
compile-time error where hmmm3's type could not be directly deduced
and where the compiler is demanding more type information on either
the left-hand side or the right-hand side such as: auto int hmmm3 =
p+q; or auto hmmm3 = int( p+q ); )
[ AMBIGUITY REGARDING ZERO ]
I realize that numerous programmers use the macro NULL throughout
their code instead of using 0 to represent the null pointer, but for
the purpose of this illustration, I will use 0 just as what the
post-preprocessor/preprocessed input to the compiler uses. (For
anyone who thinks that I am using integer 0 in some sort of icky
bitwise transformation of an integer into a pointer [I am not; the
0/null-pointer need not be represented by all bits having value 0.]
and/or that NULL would be different in any way than 0 [It would not.],
please read section 4.10 [conv.ptr] of C++98 standard before
replying.)
Consider the following:
void barCanonical( const char* in )
{
char* p = 0;
p = in;
*p = 'Z';
}
Obviously p is a pointer initialized to the null-pointer. Obviously
the value of p[0] is 'Z'.
Consider the following:
void bar( const char* in )
{
auto p = 0;
p = in; // legal? or illegal?
*p = 'Z'; // legal? or illegal?
}
Note that just as proponents of a new language mechanism for
type-implied-by-initialization want the compiler to deduce the correct
type of a variable declaration or constant declaration from context,
years ago people wanted the compiler to deduce the correct type of 0
based on context: integer versus pointer. I suspect that a debate
could occur where some of the positions would be:
1) the compiler should reject this type-implied-by-initialization
because zero does have have a single type in C++. (Actually, zero
does have the single type in C and C++ of integer, but it is
implicitly convertable to pointer on demand.) Zero may be deduced in
some contexts to be an int and may be deduced to be a null-pointer in
other contexts. (Note that 0L is certainly an signed long int, 0UL is
certainly an unsigned long, 0U is certainly an unsigned int, 0.0 is
certainly a double, 0.0F is certainly a float, and 0.0L is certainly a
double, but 0 unadorned is deduced to be either an int or a
null-pointer depending on context. Deducing whether 0 is intended to
be an integer or intended to be a pointer is impossible in this case,
leaving only assuming instead of deducing.)
2) the compiler should at all times assume that zero is the int
usage and not the null-pointer usage. Thus the questioned lines of
code would be illegal. Some programmers may have difficulty finding
this error because the root-cause line of code is not where the errors
were reported.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk