C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Unary dereference/addressof as function objects

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sun, 27 Aug 2023 19:47:49 -0800
On Thu, Aug 24, 2023 at 10:56 AM Giuseppe D'Angelo via Std-Proposals <
std-proposals_at_[hidden]> wrote:

>
> Has anyone ever proposed or thought of proposing to add the unary *
> operator (and possibly unary &) as function objects in <functional>? I
> find myself using * quite often (in algorithms, range pipelines, ...),
> it would be nice to have it as a standard function object; the
> implementation is trivial.
>
> As of operator&, I don't think I've ever had the need for it, but I
> guess it should be proposed together for symmetry.
>

I've certainly thought of (and wished-existed) some kind of function object
for unary asterisk, yeah. Specifically for an example like this:
    const std::vector<ExpensiveBigNum> input = {3,1,4,1,5,9,2,6,5};
    std::vector<const ExpensiveBigNum*> temp(input.size());
    std::ranges::transform(input, temp, std::ampersand<>()); // fantasy
syntax instead of a lambda
    std::ranges::sort(temp, {}, std::asterisk<>()); // fantasy syntax
instead of a lambda
    std::ranges::transform(temp,
std::ostream_iterator<ExpensiveBigNum>(std::cout, "\n"), std::asterisk<>());
That's the "before picture" that motivates STL Classic's
`std::reference_wrapper`. The "after picture" is:
    const std::vector<ExpensiveBigNum> input = {3,1,4,1,5,9,2,6,5};
    std::vector<std::reference_wrapper<const ExpensiveBigNum>>
temp(input.begin(), input.end());
    std::ranges::sort(temp);
    std::ranges::copy(temp,
std::ostream_iterator<ExpensiveBigNum>(std::cout, "\n"));
[Dictated, not compiled; beware of typos]

I think I've desired `std::asterisk` in production code too, but I don't
remember why. I specifically recall desiring `std::asterisk` but *not* thinking
of `std::ampersand` — which would be weird, if my only motivating example
were the code above, because the code above uses both.

Since I foresee an inevitable bikeshedding on the naming: does anyone
> possibly know where the currently existing function objects got their
> names from? Why is std::divides a verb in the third-person singular
> form, but std::negate is not? Why are those verbs, and e.g. std::plus is
> a noun instead?
>

FWIW, I don't think "plus" and "minus" in English are nouns; I think
they're prepositions like "over", or maybe conjunctions like "but". My wild
hypothesis is that Stepanov-or-whoever started with nothing but `plus` and
`minus` (which are actually used in the STL, e.g. `std::accumulate` uses
`std::plus`), and then the rest were designed-by-committee to match those
names in mouthfeel. "A plus B, A minus B, A times B" are obvious no matter
whether you're thinking of them as prepositions or as the names of the
ASCII operators... except that "std::times" would be confused with
`std::time`, so it was turned into `std::multiplies`, and then
`std::divides` follows from *that* pattern (even though it would have been
`std::divided_by` if they'd stuck with the preposition idea). And then
(finally!) they shifted to naming the lifted objects after the operations
they represent, e.g. `std::negate`, `std::bit_and`, `std::bit_not` (not
`std::tilde`!).
(Note that `std::shift_left`, `std::shift_right` have been claimed for
another purpose <https://en.cppreference.com/w/cpp/algorithm/shift>, but
`std::left_shift` and `std::right_shift` remain available.)

In hindsight, I think it would actually have been great to name all the
lifted operations after the source code text they represent: `a + b` is
`std::plus`, `a < b` is `std::less`, `a / b` is `std::slash`, `a * b` is
`std::asterisk`, `a == b` is `std::equal_equal`, `a = b` is `std::equal`,
`a += b` is `std::plus_equal`, and so on and so forth. Then it would be
quite natural that `std::minus()(5, 8) == std::minus()(3)`. Of course this
would have caused confusion again with C++17 fold-expressions: should
`std::minus` actually be variadic, such that `std::minus(ts...) == (ts -
...)`?
*If* they'd all been done that way, then you'd already have gotten unary
asterisk and unary ampersand for free, as overloaded single-argument
`operator()`s on the exact same function objects that are currently named
`std::multiplies` and `std::bit_and`. But I think that ship has sailed.
Still might be worth mentioning in the paper, if only as "considered and
rejected."

4) std::dereference (std::dereferences?)
>
> This is the logical operation, and my preferred choice. But does this
> mean that operator& should be spelled, for symmetry, std::reference(s)?
> Eww.
>

I don't think operator& is known as "the reference operator," so I don't
think the symmetry argument holds any water (thank goodness).
My three suggestions are
    std::asterisk / std::ampersand [probably too inconsistent at this
point in history]
    std::dereferences / std::addresses ["addresses" is pretty generic and
easily mistaken for a plural noun but otherwise fits the mouthfeel]
    std::dereferences / std::address_of ["address_of" as used in N3421
paragraph "F."; having all three of `addressof` `to_address` `address_of`
is no *more* confusing than, say, C++20 having all four of `is_eq`
`cmp_equal` `equal` `equal_to`]

N3421 implies that STL might have chosen "std::indirection" for operator*,
but I agree I don't like that idea.

FWIW I weakly discourage the paper — it doesn't seem worth the committee's
time to me — but if you do it, *please* propose names for all of the
remaining operators in one fell swoop! (Even the ones that won't *exist* in
the concrete standard language should *have names carved out* in the
Platonic space.) The current mishmash is due to people just proposing one
or two at a time. The way you avoid future mishmash is to design the whole
thing up front, even if it doesn't all get in at once (or ever). This also
lays useful groundwork for the *next* guy, 3–6 years from now, proposing
the *next* one or two operators, because you'll have designed the roadway
for them and all they'll have to do is follow it.

Speaking of relying on past work: definitely glean through Stephan T.
Lavavej's N3421 for names; and any other past related proposals you can
find.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3421.htm

Also in the "Alternatives Considered" section, you'd want to ask whether we
could just make `std::addressof` a niebloid object, and/or introduce a
`std::ranges::addressof` niebloid. Thus:
    std::ranges::transform(input, temp, std::addressof); // fantasy syntax
if std::addressof were a niebloid object
(Note in this context by "niebloid object" I just mean "CPO that's not
customizable." I understand there *are* still people out there pushing for
niebloids to be implemented as function-template overload sets that
interact unusually with ADL but still can't be passed around; that's *not*
what I mean here, of course.)

my $.02,
Arthur

Received on 2023-08-28 03:48:03