[comp.lang.ada] Reference vs. copy semantics in passing parameters

mfeldman@seas.gwu.edu (Michael Feldman) (02/17/91)

In article <jls.666659373@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
>There is an unsafe aspect of passing access types as IN parameters in Ada
>that is, sad to say, handled rather better in C++.
>
>In Ada, you can pass an access type to a function as an IN:
>	type Some_Foo...
>        type Pointer is access Some_Foo;
>        function Some_Bar (Some_Param : in Pointer)...
>And then, inside the function, dereference the pointer and modify the
>pointed-to construct.
>
>In C++, you can declare not only the pointer constant but the pointed
>to construct constant as well. This allows passing by reference in a
>read-only manner, which is NOT possible in Ada at present.

Well now I'm curious. Given that only scalars and small structures are
usually passed by copy, why would you want to guarantee reference
passing for read-only parameters in such a kludgy way? Ada provides you
with everything you need:

- parameters large enough to cause performance concerns are passed by
  reference anyway (in any reasonable implementation, anyway);

- IN parameters are read-only, no matter how they are passed.

Am I missing some important other issue here? What aren't you getting
from this combination of features?

Mike Feldman

PS: It seems to me that Ada9x could clarify the issue by simply requiring
that structured parameters be passed by reference (instead of the Ada83
rule that it's implementation-dependent). Since a program whose behavior
depends upon the method of passing is - by definition of the LRM -
erroneous, the only programs that would break would be erroneous ones,
which Ada9x says it doesn't care about. So the clarification would be
upward compatible. Ada9x-ers: what would be the objections?

sommar@enea.se (Erland Sommarskog) (02/18/91)

Also sprach Micheal Feldman (mfeldman@seas.gwu.edu):
>Jim Showalter (jls@yoda.Rational.COM) writes:
>>In C++, you can declare not only the pointer constant but the pointed
>>to construct constant as well. This allows passing by reference in a
>>read-only manner, which is NOT possible in Ada at present.
>
>Well now I'm curious. Given that only scalars and small structures are
>usually passed by copy, why would you want to guarantee reference
>passing for read-only parameters in such a kludgy way? Ada provides you
>with everything you need:

Don't get confused by terminology. Forget about passing mechanisms.
Remember data abstraction.

If you have a routine which takes a pointer parameter as IN, you
are guaranteed that the *pointer* don't change, but the object
pointed to may be completely altered. For instance look at:

   PROCEDURE P(X : IN ADT);

Am I as a caller that Do_something from this signature guaranteed
that P does not change X? Answer: No. If I look in the package
specification I find that ADT is limited private. Only if I cheat
and sneak into the private part and find that ADT is declared as
a pointer-less type I know that X won't change. If there is a pointer,
and often that's all an ADT is, P may change X just as much as it
wants. Remember that as a user, I may perceive X as a completely
static object.

The practical implication is on the side of the ADT implementor. He
may choose to define ADT as a pointer in order to be able to defer
the record definition to the package body, and thereby reducing
recompilation impact in case he adds or removes a field. The cost
of this is that he loses compiler control of inadvertent changes
to IN parameters, and we're back on the Pascal level.

>PS: It seems to me that Ada9x could clarify the issue by simply requiring
>that structured parameters be passed by reference (instead of the Ada83
>rule that it's implementation-dependent). Since a program whose behavior
>depends upon the method of passing is - by definition of the LRM -
>erroneous, the only programs that would break would be erroneous ones,
>which Ada9x says it doesn't care about. So the clarification would be
>upward compatible. Ada9x-ers: what would be the objections?

Ada should not bind the implementors in order to make ugly
programming unambiguous. Rather I think Ada is going too far
when requiring a certain implementation in some situations.
The IN, OUT and IN OUT modes are a beautiful invention with
exception of the problems mentioned above.
-- 
Erland Sommarskog - ENEA Data, Stockholm - sommar@enea.se
"...I cannot understand someone saying 'Rush makes pretty good music, 
but they ARE commercial'." -- William J Bouma

jls@yoda.Rational.COM (Jim Showalter) (02/18/91)

Yes, but you miss my point (I think?). It is not modification of the
POINTER we are trying to prevent. We are trying to prevent modification
of what the pointer points to, and there is currently no way to insure
this in Ada. A doofus can break the rules with impunity.

Why would it BE a pointer? Beats me, but these things do happen from time
to time, and when they do it would be nice if the language made them safe.

One gross thing that people do currently is give IN/OUT semantics to
parameters on functions by passing a pointer and dereferencing it inside
the function to modify what the pointer points to. There is no way to
prevent this egregious behavior at present (note: many many people have
also requested that 9x include IN/OUT's on functions, which would make
the case for performing the trick just described even weaker).
--
***** DISCLAIMER: The opinions expressed herein are my own. Duh. Like you'd
ever be able to find a company (or, for that matter, very many people) with
opinions like mine. 
                   -- "When I want your opinion, I'll beat it out of you."

mfeldman@seas.gwu.edu (Michael Feldman) (02/18/91)

In article <jls.666837388@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
>Yes, but you miss my point (I think?). It is not modification of the
>POINTER we are trying to prevent. We are trying to prevent modification
>of what the pointer points to, and there is currently no way to insure
>this in Ada. A doofus can break the rules with impunity.
Hmmm.Are you implying that C++, or whatever, can guarantee that an entire
linked list could be kept safe from modification by making its head pointer
an IN parameter? Neat idea, but how on earth could it be implemented?

If you mean only the directly designated value, i.e. the node (say) that
the pointer points to but not the other nodes "down the line", I don't
really think you've accomplished very much.
>
>Why would it BE a pointer? Beats me, but these things do happen from time
>to time, and when they do it would be nice if the language made them safe.
Well, this is a matter of taste. It's impossible (or nearly, IMHO) to make
the language safe from people who insist on using side effects. The goal,
it seems to me, is to make it difficult to _accidentally_ do stupid things.
If people want to do them deliberately, they will find a way no matter how
safe we think we are building things.
>
>One gross thing that people do currently is give IN/OUT semantics to
>parameters on functions by passing a pointer and dereferencing it inside
>the function to modify what the pointer points to. There is no way to
>prevent this egregious behavior at present (note: many many people have
>also requested that 9x include IN/OUT's on functions, which would make
>the case for performing the trick just described even weaker).
A dumb idea, if you ask me. People are trying to make Ada behave like C.
If they try hard enough to subvert the safety that Ada provides, they will
find a way to succeed. Ada9x is looking at this issue (it's in the
requirements document, but I don't recall if it's a requirement or a study
topic); I hope they leave this as it is in Ada83.

As I recall, the argument for allowing IN/OUT's on functions is that
since nonlocal references are allowed on functions, allowing IN/OUT's  
is a clearer way of doing the same thing. There is a certain logic to
this, I must say. But if you encapsulate the function in a package, using
the nonlocal reference only to maintain state data, with the client unable
to muck with the state variable, then an IN/OUT parameter does _not_
accomplish the same thing, because it exposes the state variable to the client.

Mike Feldman

madmats@elcgl.epfl.ch (02/18/91)

In article <2742@sparko.gwu.edu>, mfeldman@seas.gwu.edu (Michael Feldman) writes:
> 
> PS: It seems to me that Ada9x could clarify the issue by simply requiring
> that structured parameters be passed by reference (instead of the Ada83
> rule that it's implementation-dependent). Since a program whose behavior
> depends upon the method of passing is - by definition of the LRM -
> erroneous, the only programs that would break would be erroneous ones,
> which Ada9x says it doesn't care about. So the clarification would be
> upward compatible. Ada9x-ers: what would be the objections?

I think the objections would be that there should be nothing in Ada that
prevents its implementation on a distributed system (without shared
memory). Remote procedure calls and entries cannot reasonably be done
if the reference mechanism is used for parameter passing, even for large
structures.

Mats

smithd@software.org (Doug Smith) (02/19/91)

In article <2635@enea.se> sommar@enea.se (Erland Sommarskog) writes:
> Also sprach Micheal Feldman (mfeldman@seas.gwu.edu):
> >Jim Showalter (jls@yoda.Rational.COM) writes:
> >>In C++, you can declare not only the pointer constant but the pointed
> >>to construct constant as well. This allows passing by reference in a
> >>read-only manner, which is NOT possible in Ada at present.
> >
> >Well now I'm curious. Given that only scalars and small structures are
> >usually passed by copy, why would you want to guarantee reference
> >passing for read-only parameters in such a kludgy way? Ada provides you
> >with everything you need:
> 
> Don't get confused by terminology. Forget about passing mechanisms.
> Remember data abstraction.
> 
More specifically, notice how an ADT can be specified to control the
ambiguity of parameter modes for pointers to complex data structures.
Although a particular implementation of linked list might always
initialize a header record, then never need to change it during
Prepends, Appends, etc., IN OUT can enforce the intent of the
operations.  For example:

    package Linked_Integer_Operations is
        type Lists is limited private;

        procedure Append (List    : in out Lists;
                          Element : in     Integer);

    private
        ... Declare Lists as an initialized record whose value
            doesn't have to change during the Append operation.
    end;

Now the application engineer cannot accidently change a list that he
did not originally intend to change:

    procedure Reorder_List (New_List : in out Lists;
                            Old_List : in     Lists) is
    begin
        Append (Old_List, 0); -- will not compile !!!
    end;

    --    RM 6.4.1(3): actual must be a variable name or a conversion
    --    RM 6.2(3): assignment to an IN parameter not allowed

No matter how arrogant I become about my own programming skills, this
kind of compile time checking has saved me many headaches!

ae@sei.cmu.edu (Arthur Evans) (02/19/91)

Jim Showalter (jls@yoda.Rational.COM) comments that Ada 9X might permit
functions to have parameters of mode IN OUT or OUT.

An early draft of the Requirements Document included a requirement for
such a change.  However, because of many comments opposing the change it
was removed from the final document.

Art Evans

mfeldman@seas.gwu.edu (Michael Feldman) (02/19/91)

In article <16152@as0c.sei.cmu.edu> ae@sei.cmu.edu (Arthur Evans) writes:
>Jim Showalter (jls@yoda.Rational.COM) comments that Ada 9X might permit
>functions to have parameters of mode IN OUT or OUT.
>
>An early draft of the Requirements Document included a requirement for
>such a change.  However, because of many comments opposing the change it
>was removed from the final document.

A good decision, IMHO. A function should be as nearly a "black box" as
possible, which has no side effects. In Ada a function that has state
memory (a pseudo-random number generator, for example), must have a side
effect (of modifying a variable of the package body it's in, presumably).
This could have been prevented if Ada had allowed _static_ data structures
in subprograms, a la PL/1, C, and Fortran. But alas, it doesn't, and I'm
sure it won't.

But in any case, the client of the function should not be able to cause
side effects. Just because other languages have "value-returning procedures"
or "functions with side effects" (dpending on how you want to look at it)
doesn't mean Ada needs to. Let functions be functions.

Mike Feldman

eachus@aries.mitre.org (Robert I. Eachus) (02/20/91)

In article <2742@sparko.gwu.edu>, mfeldman@seas.gwu.edu (Michael Feldman) writes:
   > 
   > PS: It seems to me that Ada9x could clarify the issue by simply requiring
   > that structured parameters be passed by reference (instead of the Ada83
   > rule that it's implementation-dependent). Since a program whose behavior
   > depends upon the method of passing is - by definition of the LRM -
   > erroneous, the only programs that would break would be erroneous ones,
   > which Ada9x says it doesn't care about. So the clarification would be
   > upward compatible. Ada9x-ers: what would be the objections?

   There are two other reasons for allowing arrays to be passed by
copy: descriptors and slices.  For example, if I pass a STRING to a
Text_IO routine, it will almost certainly require a descriptor be
passed also. However, if I declare a constrained sting type:

  type MyString is array (Integer range 1..10) of Standard.Character;

Then no descriptor is required to call:

  procedure FOO(S: in MyString);

So far so good, but now let us declare a record type with no room for
descriptors: 

  type MyRec is record S: String(1...10); end record;
  for MyRec'SIZE use 80;
  MR: MyRec := (S => "Try this..");

Now we call Text_IO.Put_Line(MR).  Where does the descriptor come
from?  The easy solution is to make this call by copy, and generate
a descriptor for the area copied to.  A similar problem occurs if an
array is packed as part of a record--unpacking produces a copy.

As for slices try:

   type Bit_Array is array (Integer range <>) of Boolean;
   pragma PACK(Bit_Array);
   X: Bit_Array(1..1000) := (others => FALSE);
   
Now how do you handle X(2..11) := X(30..39) and X(100..109); ?
Most compilers will copy the slices so that the copies begin on byte
or word boundaries rather than impose the overhead of bit level
pointers on all subprograms which have parameters of such types. (This
way you only incur the cost when you use such a feature, and in the
above example  the generated code will probably be much more efficient
in the pass by copy case.

   Notice that in both cases the decision as to whether to pass by
copy or by reference is made when the call is compiled not when the
subprogam itself is compiled.  This is the reason for the standard
warning that the compiler is allowed to change the parameter passing
mechanism from call to call, and even from one invocation to the next.
For example, in the bit slice example, if you have:

     X(A..B) := X(C..D) and X(E..F);

the generated code might call a run-time routine which will makes a
copy only when A, C or E is not a multiple of eight, so on a
particular call one parameter might be passed by copy and the other by
reference.
--

					Robert I. Eachus

     Our troops will have the best possible support in the entire
world.  And they will not be asked to fight with one hand tied behind
their back.  President George Bush, January 16, 1991

blakemor@software.org (Alex Blakemore) (05/16/91)

In article <2742@sparko.gwu.edu> mfeldman@seas.gwu.edu () writes:
> In article <jls.666659373@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
> In C++, you can declare not only the pointer constant but the pointed
> to construct constant as well. This allows passing by reference in a
> read-only manner, which is NOT possible in Ada at present.

This sounds like a nice safety feature but can callers really rely on it ?
Even if the C++ language prevents updating the object if the pointer is
declared appropriately, does it prevent assignment to a normal pointer
which will allow the referenced object to be updated ?

I know very little about C++, but will attempt to pose an argument
against this feature in Ada terms.  Perhaps someone with C++ knowledge
could explain why this feature is really desirable ?

Consider this Ada flavored example, where constant means the referenced
object may not be updated.

procedure look_at_object (p : in CONSTANT ptr_to_object);

procedure look_at_object (p : in CONSTANT ptr_to_object) is
  temp : ptr_to_object;
begin 
  temp := p;  -- is this legal in C++ ?
  p.all := anything;
end;

Unless C++ prevents the assignment to another pointer above, then the feature
can be easily subverted (purposely or accidently).  that means callers have to
  a. trust that the body really obeys the implication in the parameter mode to not
     update the referenced object 
or
  b. examine the body to make sure.

  Since the new mode cannot be really trusted (as defined above),
it doesnt really aid in reasoning about a program's behavior.
It can prevent some simple errors that are relatively easy to spot 
(e.g. p.anything on lhs of assignemnt) but can't prevent the more
tricky types of errors.  It can even do harm by giving a false sense of assurance.

 Of course this argument falls apart if you invent more rules to prevent
making copies of such pointers in anyway, but can you do that without
making the language even more complex?  Besides the assignment you'ld have to
make sure parameters of such modes could only be passed by the same mode
to other subprograms.
-- 
---------------------------------------------------------------------
Alex Blakemore           blakemore@software.org        (703) 742-7125
Software Productivity Consortium  2214 Rock Hill Rd, Herndon VA 22070

neeri@iis.ethz.ch (Matthias Ulrich Neeracher) (05/17/91)

In article <1991May16.135103.1688@software.org> blakemor@software.org (Alex Blakemore) writes:
>In article <2742@sparko.gwu.edu> mfeldman@seas.gwu.edu () writes:
>> In article <jls.666659373@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
>> In C++, you can declare not only the pointer constant but the pointed
>> to construct constant as well. This allows passing by reference in a
>> read-only manner, which is NOT possible in Ada at present.
>
>This sounds like a nice safety feature but can callers really rely on it ?
>Even if the C++ language prevents updating the object if the pointer is
>declared appropriately, does it prevent assignment to a normal pointer
>which will allow the referenced object to be updated ?
>[...]
>Consider this Ada flavored example, where constant means the referenced
>object may not be updated.
>
>procedure look_at_object (p : in CONSTANT ptr_to_object);
>
>procedure look_at_object (p : in CONSTANT ptr_to_object) is
>  temp : ptr_to_object;
>begin 
>  temp := p;  -- is this legal in C++ ?
>  p.all := anything;
>end;
>
>Unless C++ prevents the assignment to another pointer above, then the feature
>can be easily subverted (purposely or accidently).

[Sorry to post C++ to comp.lang.ada, but the question is posted here]

In C++, the above assignment is illegal, so the risk of subverting the feature
accidentally is low. On the other hand, the assignment can easily be done if an
explicit type cast to the non-constant type is employed in the right-hand side,
so the feature can indeed easily be subverted purposely.

I tend to agree with this design philosophy, as I believe that it is beneficial
to try to guard programmers from their own stupid... I mean fallibility, but that
there is little use of trying to control their malice with programming language
features. A borderline case is, of course, programmer's lazyness.

Matthias

-----
Matthias Neeracher                                      neeri@iis.ethz.ch
   "These days, though, you have to be pretty technical before you can 
    even aspire to crudeness." -- William Gibson, _Johnny Mnemonic_

sakkinen@jyu.fi (Markku Sakkinen) (05/17/91)

In article <1991May16.135103.1688@software.org> blakemor@software.org (Alex Blakemore) writes:
>In article <2742@sparko.gwu.edu> mfeldman@seas.gwu.edu () writes:
>> In article <jls.666659373@yoda> jls@yoda.Rational.COM (Jim Showalter) writes:
>> In C++, you can declare not only the pointer constant but the pointed
>> to construct constant as well. This allows passing by reference in a
>> read-only manner, which is NOT possible in Ada at present.

I think the original poster has erred here: nothing prevents an Ada
implementation from passing 'in' parameters by reference whenever that
is most convenient or efficient.  (I think Ada's 'in' parameters are
a significant improvement over the value parameters of Algol and Pascal.)
However, the 'pointer to constant' feature of C++ may be
worth discussing in this newsgroup, because its applicability is _not_
restricted to parameter passing.

The meaning of 'const' in C++ should be noted.  If Ada had the same approach
to constants, we could have something like:
   type object;
   type immutable is constant object;
   type ptr_to_object is access object;
   type ptr_to_immutable is access immutable;
An 'immutable' value could be directly assigned to an 'object' variable,
and a 'ptr_to_object' value to a 'ptr_to_immutable' variable.
Thus, types like 'ptr_to_immutable' are, strictly speaking, not
"pointer to constant" types, but rather "non-modifying pointer" types:
the object pointed to may well be modified, but not via this reference.

>This sounds like a nice safety feature but can callers really rely on it ?
>Even if the C++ language prevents updating the object if the pointer is
>declared appropriately, does it prevent assignment to a normal pointer
>which will allow the referenced object to be updated ?

Of course, you cannot _really_ rely on anything in a C-based language:
by fooling around with pointers you can even overwrite _code_ unless
the operating systems protects code segments.  Such things are "illegal"
in principle, although it is difficult to imagine a C or C++ implementation
that could catch them.  --  But indeed, the kind of assignment (or cast)
that you asked about is fully _legal_ C++ according to the most
authoritative definition!

>I know very little about C++, but will attempt to pose an argument
>against this feature in Ada terms.  Perhaps someone with C++ knowledge
>could explain why this feature is really desirable ?
>
>Consider this Ada flavored example, where constant means the referenced
>object may not be updated.
> ...
>procedure look_at_object (p : in CONSTANT ptr_to_object) is
>  temp : ptr_to_object;
>begin 
>  temp := p;  -- is this legal in C++ ?
                  ^^^^^^^^^
>  p.all := anything;
>end;

An assignment corresponding to the above is _not_ legal in C++,
the legal case would correspond ("Adaified") to:
   temp := ptr_to_object (p);
Using my previous type declarations, the procedure header would be:
   procedure look_at_object (p : in ptr_to_immutable)

Actually, this whole example is not very convincing of the need
for such pointer types.
If we take the above C++-like meaning of "non-modifying pointer",
the procedure could just as well be declared as
   procedure look_at_object (o : in object)
If we would like a true "pointer to constant" meaning, then:
   procedure look_at_object (i : in immutable)

>Unless C++ prevents the assignment to another pointer above, then the feature
>can be easily subverted (purposely or accidently).  [...]

Yes, but I think that the feature combined with some programming discipline
is nevertheless useful.  Of course, if this feature were to be
introduced in Ada (or some other language with stronger typing than C++),
one would probably not allow such dangerous conversions.
(They fit the general spirit of C and C++, but not the spirit of Ada.)

> ...
> Of course this argument falls apart if you invent more rules to prevent
>making copies of such pointers in anyway, but can you do that without
>making the language even more complex?  Besides the assignment you'ld have to
>make sure parameters of such modes could only be passed by the same mode
>to other subprograms.

Since "pointer to constant something" is regarded as a distinct type
and not as a specific device for parameter passing, I don't think it
would cause any "cascading" complexity;  existing type rules would
be quite sufficient.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

ka@felix.UUCP (Kenneth Almquist) (05/28/91)

>In article <2742@sparko.gwu.edu> mfeldman@seas.gwu.edu () writes:
>> In C++, you can declare not only the pointer constant but the pointed
>> to construct constant as well. This allows passing by reference in a
>> read-only manner, which is NOT possible in Ada at present.

This feature has also been included in the ANSI C standard.

blakemor@software.org (Alex Blakemore) asks:
> This sounds like a nice safety feature but can callers really rely on it ?
> Even if the C++ language prevents updating the object if the pointer is
> declared appropriately, does it prevent assignment to a normal pointer
> which will allow the referenced object to be updated ?

The "constant" attribute is part of the type, so the type checking system
makes this secure *unless* the type system is circumvented by the use of
a type cast.  (By "type cast", I mean the C equivalent of Ada's
UNCHECKED_CONVERSION.)  The rule is that a pointer to a non-constant
object can be assigned to a pointer to a constant object, but not the
other way around.  (Assignment includes parameter passing.)

A problem with the "constant" attribute is that it is not inherited
properly.  Consider a routine that looks up an entry in a data
structure like a tree or a string, and returns a pointer to the
element found.  The returned pointer should point to a constant object
iff the data structure was declared to be constant, but the C type
system doesn't handle this.  For this reason, several routines in the
standard ANSI C library are defined in such a way that they cannot be
implemented without using type casts.
					Kenneth Almquist