C++ Logo

sg20

Advanced search

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

From: Martin Beeger <martin.beeger_at_[hidden]>
Date: Sun, 15 Dec 2019 01:44:40 +0100
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.

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
requirements".

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?

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.

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?

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

Kind regards,

Martin Beeger

Received on 2019-12-14 18:47:09