On Sun, Dec 15, 2019 at 8:56 AM Martin Beeger via SG20 <sg20@lists.isocpp.org> 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