C++ Logo


Advanced search

Re: [SG20] How much is too much with C++20 concepts?

From: Peter Sommerlad (C++) <"Peter>
Date: Tue, 3 Mar 2020 17:48:38 +0100

Arthur, you are my hero! But....

What I explain in addition, is that the case with

     template<class T> T triple(T x) { return 3 * x; }

restricts the template parameter based on its usage, that I would spell
'typename T' requires to be copyable and multiplyable with an int. This
is the actual concept of the template parameter (I am teaching the
concept of a template parameter like this at least for 10 years, without
the special syntax we get). Violating the concept, as with a string,
will result in a compile error. SFINAE and overloads allow to special
case the template, that is where C++20 concepts syntax kicks in and make
things easier to spell than typical std::enable_if...

I am not that much against mixing templates and non-templates in an
overload set, e.g., for special casing, what you must not to is trying
to specialize function templates, which get only applied, when the
generic template is selected by overload resolution, use overloads instead.


Arthur O'Dwyer via SG20 wrote on 03.03.20 17:33:
> On Sun, Mar 1, 2020 at 1:11 AM Amir Kirsh via SG20
> <sg20_at_[hidden] <mailto:sg20_at_[hidden]>> wrote:
> >
> > Substitute I mean not as a replacement for existing code. But
> rather: now you can write instead that, in new code.
> > So again the question is if (or when) the use of std::integral would
> be actually advisable over let's say long long.
> > Or what are the pros and cons.
> The first (relevant) thing to teach students is the notion of a
> /*function*/.
> int triple(int x) { return 3 * x; }
> The next thing is to teach that functions can be */overloaded/* to
> form an overload set.
> int triple(int x) { return 3 * x; }
> double triple(double x) { return 3 * x; }
> std::string triple(const std::string& x) { return x + x + x; }
> The next thing is to teach that when you have a lot of overloads that
> look similar, you want a cookie-cutter to stamp out similar overloads
> really quickly. That's a /*template*/ for making functions — a
> /*function template*/.
> template<class T> T triple(T x) { return 3 * x; }
> Should you go straight to the C++20 syntax for "terse" function
> templates? My opinion is a strong no, but I'm conservative in
> everything. ;) For the sake of argument, let's say you present the
> C++20 syntax right away (and deal with the inevitable confusion about
> the two different meanings of `auto` in this snippet):
> auto triple(auto x) { return 3 * x; }
> Now you are ready to show the problem with /mixing function overloads
> and function templates/ in the same overload set:
> auto triple(auto x) { return 3 * x; }
> std::string triple(const std::string& x) { return x + x + x; }
> int main() { triple("abc"); } // DOES NOT COMPILE
> How do we fix this? Well, the old advice would have been, simply, for
> the love of god don't mix functions and function templates in the same
> overload set! Keep it simple! However, in C++20, maybe you'd be able
> to push the complexity one step further: The problem with our "triple"
> overload set is that triple<const char*>(const char*) is a better
> match for "abc" than triple(const std::string&). It's always going to
> /be/ better. The only way to fix our overload set (other than
> completely rearchitecting it for sanity, as in the C++17 course) is to
> indicate to the compiler that triple<const char*>(const char*) /should
> never be allowed to exist/. We do that by adding a /*constraint*/ via
> a requires-clause.
> auto triple(auto x) requires std::arithmetic<(decltype(x))> {
> return 3 * x; }
> std::string triple(const std::string& x) { return x + x + x; }
> int main() { triple("abc"); } // OK!
> Finally, C++20 allows us to use another terse syntax for the simplest
> constraints. I strongly recommend against /ever/ using this syntax in
> production code, but for the sake of argument...
> auto triple(std::arithmetic auto x) { return 3 * x; }
> std::string triple(const std::string& x) { return x + x + x; }
> When you ask:
> > Then: what are the cases in which preserving the original type is a
> pro?
> > Are there any performance advantages for one of the two in certain
> cases?
> > Is one safer than the other in a way?
> it's important to realize that you are asking about the pros and cons
> of /*function templates versus non-template functions*/. The pros and
> cons have not changed since 1984.
> - Templates are polymorphic rather than monomorphic.
> - Template instantiations are individually codegenned and thus can
> give highly specialized and optimized code.
> - Template instantiations are individually codegenned and thus can
> produce "template bloat."
> - Templates can be harder to reason about, because it's like debugging
> a function where every variable is defined `auto`. (We saw that in the
> `triple("abc")` example.)
> - Templates can be harder to debug in a debugger, because it's like
> debugging an inline function. Lines of source don't correspond to
> lines of assembly.
> - Templates must be defined in header files. (Or, in C++20, they can
> be defined in modules. Modules have their own set of pros and cons.)
> In brief, templates produce /highly specialized codegen for
> polymorphic code/ but /pay a cost in being relatively harder to use
> and maintain/.
> C++20 doesn't change anything here.
> –Arthur

Peter Sommerlad
Better Software: Consulting, Training, Reviews
Modern, Safe & Agile C++
+41 79 432 23 32

Received on 2020-03-03 10:51:25