C++ Logo

sg20

Advanced search

Re: [SG20] Difficulties in teaching the use of C++20 concepts

From: Tony V E <tvaneerd_at_[hidden]>
Date: Sun, 15 Dec 2019 11:01:14 -0500
On Sun, Dec 15, 2019 at 8:56 AM Martin Beeger via SG20 <
sg20_at_[hidden]> wrote:

> Hallo Herb!
> Am 15.12.19 um 06:45 schrieb Herb Sutter:
>
>
>
> 2. I don't teach to always deduce a type. There are two forms I've always
> taught:
>
>
>
> auto x = expr; // to make the type track, deduce it
>
>
>
> auto x = type{expr}; // to make the type stick,
> commit to it
>
>
>
> both of which are still auto. So although the first form is great for
> making code less brittle under maintenance including to avoid introducing
> silent conversions/temporaries as types change, the second form is still
> always available (and actively recommended) when it's appropriate to commit
> to a particular type, and "AAA" has always taught both of these styles.
>
>
>
> However, note that the advice to use concepts constraints applies only to
> the first form, which is deduced. It’s not very useful in the second form,
> which already commits to a concrete type.
>
> Immediately deduced types, as in the second example have a much stronger
> commitment so to speak, they are not only bound to a concept, but even to a
> type. I totally agree that these should be treated as a different form of
> auto.
>
>
> 3. The "Almost" in "Almost Always Auto" is now largely gone thanks to
> C++17 guaranteed copy elision -- so these days the idiom should be renamed
> pretty much "Always Auto," though of course people are as always free to
> disagree.
>
>
>
>
>
> I favor dying on the overconstrained hill today, as I can easily break and
> fix my code to be less constrained on demand, as dying on the
> underconstrained hilll (semantically broken code compiles) hill, as this is
> hard to find out for me. So I get you, but I will not walk this path in the
> meantime.
>
> But to sum up regarding the use of auto, a post-C++20 guideline could be
> (a hopefully much more well-formulated and littered with examples version
> of the following):
>
> 1. whenever the type of the expression is mentioned on the right hand of a
> statement, just put auto on the left side for brevity instead of repeating
> yourself on the left hand.
>
> 2. whenever auto represents a generic value whose type is not known
> locally, look for a fitting named constrained and apply it for the auto
> parameter to ensure semantic correctness of the using code without loss of
> genericity.
>
> 3. whenever you develop code and you are not thinking of its correctness
> in terms of concrete types but in form of general concepts, use auto.
>
> 4. whenever you write functions, try to think of the problem in you solve
> as a abstract problem and about generic solutions, not only about one for
> your concrete types. Look for an exisisting solution to a general problem.
>
> As you apply the last rule to your coding (in line with Sean Parents
> Better Code principles) you will either find an a solution already there,
> if you don't, what you will write will now fall under rule 3, which will in
> turn then fall under rule 2.
>
> Is that an accurate representation of what the your AAA (now AA) rule
> intends to achieve and how you would advise in a C++20 world?
>
> I as and AAA skeptic can totally get behind this new version.
>
> Martin
>
>
I am/was a AAA skeptic as well, and have been waiting for Concept x = ...
(or now Concept auto x = ...) as the sweet-spot solution.

Now that we have:

auto x = expr;
Concept auto x = expr;

doesn't
int x = expr;

fit the "pattern" better than

auto x = int{expr};

ie

auto x = expr; // whatever expr says
Number x = expr; //what I generally expect
int x = expr; // what I precisely expect

The above looks like a teachable pattern to me.

What is the value of

    auto x = int{expr};

besides following a pattern of auto on the left?

It does help match cases of

   auto x = make_shared<Foo>(); // type is mentioned on the right.

Is that the main benefit? Sorry if I have missed/forgotten the benefits of
the second usage of auto.

Importantly, note this bit from Herb above (where first form is auto x =
expr): "the first form is great for making code less brittle under
maintenance including to avoid introducing silent conversions as types
change"

Yet second form (auto x = int{expr}) has a slight brittleness under
maintenance, including introducing unexpected conversions. Note

    auto x = Foo{expr}; // calls explicit constructors
    Foo x = expr; // only calls implicit constructors

If we always use the first form, we lessen the power of implicit vs
explicit constructors (they are still useful for function params however,
ie func(expr) where expr attempts to be converted to Foo)

Now, we can say auto x = Foo{expr} is "obviously" explicit, since the type
was explicitly written, but it is _precisely_ under maintenance where the
type of expr changes, and you are now calling a different constructor, one
that you weren't expecting, one that was protected with 'explicit'.

Two real-life examples, from Howard Hinnant, paraphrased:

auto ns = nanoseconds{duration}; // duration was a chrono type, but became
int, is this still what you wanted???
vs
nanoseconds ns = duration; // if duration is strong chrono type, does the
conversion you wanted/expected, if duration is int, fails to compile

And

auto sp = shared_ptr<Foo>(ptr); // ptr is a weak_ptr, need shared
vs
shared_ptr<Foo> sp = ptr;

When, under maintenance, ptr changes from weak_ptr to raw T* ptr, one of
those lines is probably a bug, the other doesn't compile.
(And yes, maybe they should have called ptr.lock() on the weak ptr, but
some people think that was a poorly named function (confusing with mutex
etc) and prefer the conversion style.)


So I still prefer int x = expr; over auto x = int{expr};, and I think int x
= expr nicely follows a pattern with auto x = expr and Concept auto x =
expr.

-- 
Be seeing you,
Tony

Received on 2019-12-15 10:03:54