On Sun, 15 Dec 2019 at 00:44, Martin Beeger via SG20 <sg20@lists.isocpp.org> wrote:
Hello everyone!

I recently talked a lot with coworkers about C++20 and we tried together
to get a grasp around what its effective use is and what good coding
guidelines around it are.

A very fundamental change in C++20 is that Concepts are finally in (yay!).

Concept will IMHO change how the think about template arguments and
template function radically, and will give us the power to express
semantic contracts much more clearly in definition. But as one uses
concepts to modernize template functions, one quickly realizes that
nearly all template functions either have design flaws or have
constraints on template parameters which can be expressed by concepts.

This lead us to the conclusion, that one should have a coding guidelines
that flags unconstrained template parameters as a code smell. A point
that really convinced us of that this a good rule was a simple
challenge: Find me a well-defined template function, whose template
parameters are truly generic (i.e. no useful requires clauses can be
specified), and for which a test can be written that proves that it does
as advertised for all instantiations.

We really struggled to find one by looking though our codebase. The plus
function came to mind, but this requires the arguments to form a magma
at least, which is definitively a valid requires clause. The next
candidate was the identity function, but for the identity function to be
correct, its result must be identical to the input, which only has valid
meaning if the input and the output is actually equality comparable,
which is a valid requires clause.

I applaud having strict requirements on what warrants a new concept.

See P1813 for concepts relating to algebraic structures, including a magma concept. This, and the executors work, are the only two proposals that I'm aware of that look at extending the work done by N3351 (the grandparent of the ranges work).
You might like to look at these proposals for examples of how concepts are created.

To paraphrase and extend something said by Eric Niebler: type-traits describe the properties of types; concepts describe the requirements of algorithms.

Se we came up for us with the "no hidden semantic requirements"-rule,
which state that a template function should state its requirements about
input types via concepts. If they are axioms involved, use named
concepts to represent them. Where this leads us remains to be seen, but
I expect the result to be far clearer code and far better
compile-time-verification of the code.

This brings me to auto. auto-type arguments are just template
parameters. A completely unconstrained template parameter, to be
precise. While I am absolutely in favor of what we call "localized
auto", auto in cases where the lifespan is a few lines or a single scope
(e.g. for loops, iterators, make_unique results & co.), auto in
interfaces obscures intent and breaks the rule of "no hidden semantics

So we extended the guideline to: avoid auto in interfaces in C++20,
especially with template parameters, as there now is a clear better
alternative: using the appropriate concept. That, unfortunately didn't
go well.

The C++ Committee Members like Herb Sutter have preached "Almost always
auto" for almost a decade, and so our guidelines seems to oppose that
C++ guru wisdom. Also it is a lot more convenient to not have to think
about the semantics of your function and just write auto everywhere. So
I went looking, for an cite-able reference that says: "when both apply,
prefer concepts over almost always auto" and did not find prominent
references about it. Does anyone have some links of reference for me I
can cite, which clearly state, that in a C++20 world, almost always auto
is no longer the right thing to do? Given that a well-thought out
concept already exists and is defined, do you think we should prefer the
concept over auto?

Concepts are a natural extension to the Always Auto idiom. Consider integral auto x = 0;, which follows part of the Always Auto idiom, but also relies on concepts to indicate what you expect the type to model. I tend to use auto return types for a lot of detail implementations, but should probably look at the syntax for constrained-auto return types.

(Note that I'm calling it "Always Auto", since as of C++17, there aren't any situations where using auto for variables is invalid.)

But even if I manage to convince that auto in interfaces has had its
time, but from C++20 on is a smell in new code, the next problem arises.
If we want to have a significant motion away from auto, we must clearly
discourage its use in interfaces. But a rule "avoid auto in interfaces"
seemed to confuse other in practice. A lot. So I tried to figure out
what happened. The problem was: the shorthand notion for concepts was
"void func(ForwardIterator auto it)". From a teaching standpoint it is
incredibly confusing that the shorthand notion, which should be
preferred over auto, contains a semantically meaningless auto! A rule
along the lines of "just avoid auto" is much simpler than to teach than
a rule along the lines of "just avoid auto, except when there is this
confusing thing which spells auto but is not really a semantic auto, as
it will not match what auto normally matches". So a a result, this
shorthand notion tends to be avoided out of misinterpretation of the
guidelines, which then makes concepts unnecessarily hard to use.

"Just avoid X" without sound reasoning on why is how FUD and myth come about, and that's not good for teaching. Instead of "just avoid auto", I'd recommend "prefer constrained auto" or "use auto on the left, and possibly a type on the right". One of the biggest misconceptions I see about the Always Auto idiom is that people tend to think it means always do auto x = y, which is not true. AA means that explicit types only appear on the right, and fits in with type aliases.

I can understand that a syntax of "void func(ForwardIterator it)" raised
some eyebrows, but what is the reasoning behind preferring a syntax of
"void func(ForwardIterator auto it)" over alternatives like "void
func(concept ForwardIterator it)" or similar? Or am I missing a way to
consistently tell this story and not confuse people?

In both cases, this is literally the same as defining an object using struct example object;. I'd say using auto communicates "this type is going to be deduced to model the requirements of forward_iterator", rather than concretely saying "this thing is a forward_iterator", which is misleading (it could also model bidirectional_iterator). I wasn't involved in the development of the language feature, so I can only communicate how the two alternatives read to me.

Has this been talked about or what is the general plan to get our
community to move away from unconstrained template parameters and auto?

I'm eventually going to be writing a proposal on guidelines for teaching generic programming, that incorporates my experience with teaching generic programming at CppCon classes, but I don't expect this to land till Varna at the earliest.


Kind regards,

Martin Beeger

SG20 mailing list