C++ Logo

sg20

Advanced search

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

From: Christopher Di Bella <cjdb.ns_at_[hidden]>
Date: Sun, 15 Dec 2019 13:48:45 +0000
On Sun, 15 Dec 2019 at 00:44, Martin Beeger via SG20 <sg20_at_[hidden]>
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 <https://wg21.link/p1813> for concepts relating to algebraic
structures, including a magma concept. This, and the executors
<https://wg21.link/p0443> work, are the only two proposals that I'm aware
of that look at extending the work done by N3351 <https://wg21.link/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
> 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?
>

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.

Chris


>
> Kind regards,
>
> Martin Beeger
>
>
> --
> SG20 mailing list
> SG20_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg20
>

Received on 2019-12-15 07:51:21