C++ Logo

std-proposals

Advanced search

Re: Proposal: template function solution to Pythonic optional function arguments

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Tue, 21 Jul 2020 10:53:04 -0400
On Mon, Jul 20, 2020 at 5:27 PM codusnocturnus via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On Monday, July 20, 2020 1:43 PM, Jason McKesson via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
> > On Mon, Jul 20, 2020 at 2:23 PM codusnocturnus via Std-Proposals
> > std-proposals_at_[hidden] wrote:
> >
> > > Having the code-bloat associated with defining and naming a struct, as
> well as pulling in std::optional all over the place to get the equivalent
> of named parameters is objectively terrible (I think you even questioned
> the viability), and, subjectively, it looks awful at the call site, too.
> >
> > Or, you could just not use named arguments. [...]

> if you can avoid having functions take large numbers of parameters
> > to begin with, then you don't really need them.
>
> Sure, a function with lots of (especially defaulted) parameters is usually
> a code smell, but not always.
>
> That argument mostly only holds for greenfield and personal projects
> anyway. Some of us live in the world of (very dirty) brownfield projects,
> and named parameters would provide a much easier way to make legacy code
> more maintainable than trying to get budget to reach the perfect
> abstraction.
>

FWIW, I think this is overly dismissive (in the logical sense, not the tone
sense). Given the choice between a refactoring that uses only the available
C++17 tools, like taking
    foobar(a, b, f, e, c, d);
into
    AB ab = foo(a, b);
    bar(ab, f, baz(e, c, d));
, and a refactoring that uses a [hypothetical] C++2b feature such as
    foobar(theA: a, theB: b, theF: f, theE: e, theC: c, theD: d);
I don't believe that the latter will necessarily require less "budget" than
the former.
Sweeping generalization alert: I think it's easy to fall into the trap of
thinking "The current solutions seem awkward and bad, so, a new solution
will naturally be better and cheaper." Especially in the world of dirty
brownfield projects, the cost of the new solution (in compiler upgrades, in
training, in code-review friction) is going to be *at least* as high as
just "making do" with what you've already got.


...
> > > would make function overloading and default parameters as useable in
> C++ as they were intended to be. These are features that distinguish C++
> from C, but today (on this very thread) we have notable language experts
> actively campaigning against their use!
> >
> > We do? I've heard people say that default arguments should be avoided,
> > but I've never heard any "notable language experts" saying that we
> > should avoid function overloading in general.
>
> I may have overstated and/or miscontextualized, but I'm pretty sure one of
> Arthur's suggestions involved "un-overloading" a function.
> (He's active here and at C++ conferences, so I consider him both notable
> and an expert.) Granted, that was not a general "don't overload"
> suggestion - more of a "don't inappropriately overload and then use
> defaults." I think poor use of default arguments is often related to
> function overloading, as this case points out.
>

(Thanks!) Yes, that was advice against *inappropriate *overloading, not
against overloading in general. I always point to Titus Winters' talks on
overload set design:
https://www.youtube.com/watch?v=xTdeZ4MxbKo&t=4m
We may be about to prove him wrong when he says that "...it turns out
there's actually very solid agreement amongst all of the experts on what is
good and reasonable here" ;) but I would argue that when you have lots of
parameters, and especially when you have defaulted parameters which
may-be-there-or-not, it becomes very easy to violate the Google guideline
https://www.youtube.com/watch?v=xTdeZ4MxbKo&t=6m15s
"Use overloaded functions only if a reader looking at a call site can get a
good idea of what is happening without having to first figure out exactly
which overload is being called."
It strikes me just now that this guideline could probably be rephrased as
"A good function call is one where the reader knows roughly what's going to
be done with each argument and what's going to happen to its value." (This
rephrasing even encompasses other style rules, such as "Name functions
after their purpose," "Name functions with verb phrases," and "Pass
out-parameters by pointer, not by lvalue reference.")

If
    draw(42);
    draw(42, mode: -1);
is good by this criterion, then
    draw(42);
    draw_with_mode(42, -1);
is at least equally good (and probably better, in an industry context where
you don't want to re-train your people in the new style).

[
"Ah, but what if I want to make a forwarding template
`draw_with_args(Args... args) { x.draw(args...) }`? Then it's important
that these two functions share the same name!" Possible replies:
(1) Okay, that's a perfectly valid *physical *reason for them to share the
same name, which trumps the style guideline above; but note that now you're
making a tradeoff, giving up clarity for a physical necessity.
(2) Okay, but now you have to figure out how to perfect-forward named
arguments, which is going to make your WG21 proposal a lot more
complicated. Adding named parameters is unlikely to be *simple*, or it
would have been done by now! Figuring out the forwarding story is just *one
*of the many rabbit-holes involved.
(3) Frame challenge. Instead of passing the *arguments *with which to draw,
just pass a *lambda *that does the drawing however you want it to be done.
This is more flexible; it frees you up to make complicated drawing lambdas
or even named functions like `draw_with_mode`, and you aren't forced to
shove them all into the same overload set. The STL's `emplace` commits this
sin w.r.t. the constructor overload set, and I wish it didn't
<https://quuxplusone.github.io/blog/2018/03/29/the-superconstructing-super-elider/>
.
]

–Arthur

Received on 2020-07-21 09:56:34