On Sun, Mar 1, 2020 at 1:11 AM Amir Kirsh via
SG20 <
sg20@lists.isocpp.org>
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