Date: Wed, 26 Jan 2022 16:38:01 -0500
On Wed, Jan 26, 2022 at 3:03 PM Victor Eijkhout <eijkhout_at_[hidden]>
wrote:
> On , 2022Jan26, at 13:33, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
> FWIW, I teach
> - pass-by-value
> - problems: performance of all those copies; how to write an out-parameter?
> - C solution: pointers (a pointer holds a memory address)
> - pass-by-pointer
> - C++ enhancement: references (***)
> - pass-by-reference
>
>
> I agree with your sequence, and I use something very similar.
>
> But…..
>
> Why do you teach the “pass by pointer”? Maybe you and I have different
> audiences.
>
I'm sure we do. :) At least three reasons come to mind:
- My target audience is usually new-hire software engineers, who will have
taken C in college and so they already know pointers are coming; they
probably think they're scary and confusing (because their college professor
did a terrible job explaining them); there's no point trying to hide the
fact that C++ has pointers because the students are already aware of it.
Give them a quick glimpse of the monster in the first five minutes. :)
- Pass-by-pointer solves the technical machine-level problem of "how do I
pass a Widget efficiently without copying?" You just pass its address;
technical problem solved! The only problem with pointers is that they *look*
ugly. So we immediately present C++'s solution to the *looking ugly *problem,
which is to use references instead of pointers. But references aren't
magic, and they aren't inscrutable; they behave exactly like pointers.
Usually I'll fire up Godbolt at this point to show that we get the exact
same x86-64 machine instructions for `int ptr(int *p) { return *p; }` and
`int ref(int& r) { return r; }`. References *behave* like pointers, but
*look* like values; and this is why they're so awesome. They let us write
code that *looks* like it's taking a string by value, but *behaves* as
efficiently as if it were taking just a pointer. (We have already
introduced the phrase "zero-overhead abstraction" by this point.)
- Most industry codebases pass out-parameters by pointer as a matter of
style. This is a fine place to talk about that, since they'll be expected
to do it on the job.
I don't bring pointers back into the discussion until `new` and `delete`
(which again are *relatively* quickly wrapped up in C++ clothing via
`unique_ptr`, although not right away, because we have to get there via
move semantics, and move semantics are justified by
pilfering/taking-ownership, which is justified by talking about
responsibilities of ownership, and the primary interesting responsibility
to talk about is the responsibility to call `delete`).
> Agree with describing a reference as “a new name”. I usually call it an
alias. (And I stress how it is not a pointer, for the students that have
learned C)
I reserve "alias" for its technical meaning — type aliases, a.k.a.
typedefs. I call references references. But I do implicitly analogize them
to names, in that I talk about how when we assign `i = 1;` we're (of
course) not actually assigning to the *letter* `i`; we're assigning to the
object *referred to* by the name `i`. And when we assign `r = 1;`, we're
not assigning anything to `r` *itself*; we're assigning to the object *referred
to* by `r`.
@Nico:
> As with universal references become state of the art for ordinary
programmers with C++20, I wonder whether we should now teach them early or
even first.
Hard no. Forwarding references are not ordinary-programmer material, nor
should they ever be. (Ranges relies heavily on forwarding references, but
essentially *nothing* in the average programmer's life is a Ranges-style
range.)
Tangentially related: I have recently run into multiple intermediate-level
C++ programmers who knew just enough about value categories to be
*surprised* to find that `const T&` will bind to an rvalue argument — they
were, like, honestly trying to puzzle out the right way to refactor `void
f(const string&)` so that it would accept rvalue strings! (It already does
accept them, of course.)
I've quipped that `const T&` is "the O.G. universal reference," in that it
has bound to both lvalues and rvalues since C++98.
–Arthur
wrote:
> On , 2022Jan26, at 13:33, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
> FWIW, I teach
> - pass-by-value
> - problems: performance of all those copies; how to write an out-parameter?
> - C solution: pointers (a pointer holds a memory address)
> - pass-by-pointer
> - C++ enhancement: references (***)
> - pass-by-reference
>
>
> I agree with your sequence, and I use something very similar.
>
> But…..
>
> Why do you teach the “pass by pointer”? Maybe you and I have different
> audiences.
>
I'm sure we do. :) At least three reasons come to mind:
- My target audience is usually new-hire software engineers, who will have
taken C in college and so they already know pointers are coming; they
probably think they're scary and confusing (because their college professor
did a terrible job explaining them); there's no point trying to hide the
fact that C++ has pointers because the students are already aware of it.
Give them a quick glimpse of the monster in the first five minutes. :)
- Pass-by-pointer solves the technical machine-level problem of "how do I
pass a Widget efficiently without copying?" You just pass its address;
technical problem solved! The only problem with pointers is that they *look*
ugly. So we immediately present C++'s solution to the *looking ugly *problem,
which is to use references instead of pointers. But references aren't
magic, and they aren't inscrutable; they behave exactly like pointers.
Usually I'll fire up Godbolt at this point to show that we get the exact
same x86-64 machine instructions for `int ptr(int *p) { return *p; }` and
`int ref(int& r) { return r; }`. References *behave* like pointers, but
*look* like values; and this is why they're so awesome. They let us write
code that *looks* like it's taking a string by value, but *behaves* as
efficiently as if it were taking just a pointer. (We have already
introduced the phrase "zero-overhead abstraction" by this point.)
- Most industry codebases pass out-parameters by pointer as a matter of
style. This is a fine place to talk about that, since they'll be expected
to do it on the job.
I don't bring pointers back into the discussion until `new` and `delete`
(which again are *relatively* quickly wrapped up in C++ clothing via
`unique_ptr`, although not right away, because we have to get there via
move semantics, and move semantics are justified by
pilfering/taking-ownership, which is justified by talking about
responsibilities of ownership, and the primary interesting responsibility
to talk about is the responsibility to call `delete`).
> Agree with describing a reference as “a new name”. I usually call it an
alias. (And I stress how it is not a pointer, for the students that have
learned C)
I reserve "alias" for its technical meaning — type aliases, a.k.a.
typedefs. I call references references. But I do implicitly analogize them
to names, in that I talk about how when we assign `i = 1;` we're (of
course) not actually assigning to the *letter* `i`; we're assigning to the
object *referred to* by the name `i`. And when we assign `r = 1;`, we're
not assigning anything to `r` *itself*; we're assigning to the object *referred
to* by `r`.
@Nico:
> As with universal references become state of the art for ordinary
programmers with C++20, I wonder whether we should now teach them early or
even first.
Hard no. Forwarding references are not ordinary-programmer material, nor
should they ever be. (Ranges relies heavily on forwarding references, but
essentially *nothing* in the average programmer's life is a Ranges-style
range.)
Tangentially related: I have recently run into multiple intermediate-level
C++ programmers who knew just enough about value categories to be
*surprised* to find that `const T&` will bind to an rvalue argument — they
were, like, honestly trying to puzzle out the right way to refactor `void
f(const string&)` so that it would accept rvalue strings! (It already does
accept them, of course.)
I've quipped that `const T&` is "the O.G. universal reference," in that it
has bound to both lvalues and rvalues since C++98.
–Arthur
Received on 2022-01-26 21:38:13