A quick primer on type traits in modern C++
internalpointers.com
internalpointers.com
becoming more and more convinced that when sfinae opened the Turing hatch it doomed c++
I mean, even if you never saw a "constexpr" before, isn't it pretty clear from the example and the name, well, "constexpr" want's going on? Same for the other newish C++ features.
I thought it was a nice little primer.
Not a whole lot, actually. You can go from C to this in about a week if you’re focused. (I did this a few years ago when learning C++. Now if you want to talk about initialization, that’s a whole ‘nother story…)
I think that’s unfair. While “sfinae’ itself is a mouthful, what it says in practice is that template expansion observes the principle of “least surprise” which is inherently user friendly.
You certainly don’t need to know the acronym, or even of the idea, to benefit from it. I stead, the developer won’t be presented with a confusing error message when the thing they want to happen is supported but not the first possibility examined.
The argument is I think, that because sfinae effectively added an "if" statement to C++ templates, it made templates turing complete. It made it possible to write all kinds insane, undebuggable programs purely at compile-time, in the most terrible purely functional language imaginable. People did exactly that, which went fine until their colleagues (or users of open source libraries) did something unexpected and got screenfuls of errors.
Wikipedia's sfinae page has a nice story about how it added compile-time introspection and conditionals to the language: https://en.wikipedia.org/wiki/Substitution_failure_is_not_an...
Sure you can write spaghetti code, but you can do that in almost any language. And there are popular languages that had explici design goals of limiting expressiveness in the hope of reducing spaghetti code (Pascal, in its time, and go today). I consider it like a balloon: without that power you end up with more boilerplate and repeated code, which to me makes following the code harder. But other people reasonably disagree.
Using a modern example, the way template metaprogramming started feels like return-oriented programming, or those "MOV is Turing-complete" or "MPU error handling is Turing-complete tricks". Someone figured out the standard unintentionally lets you make a conforming compiler do computations, and it ended up becoming an accepted feature, instead of updating the language to something resembling modern constexprs, or Common Lisp's compiler macros.
It's a clean, minimal, purely functional Lisp-like language. Nothing 'insane' or 'undebuggable' about it.
(Well, maybe if you're the kind of person who only ever coded Qt-style OOP C++98 in your life you might be shocked, but for the rest of us there is nothing surprising or special about C++ templates.)
But what do I know, I haven't done C++ for about 5 years and was never that into it.
For new code it is to be preferred - it generates less symbols than enable_if, compiles faster (by a noticeable margin from my own experience) and makes for more readable code as you don't have to go look everywhere for the various enable_if cases.
The right solution are of course concepts.
When you use enable_if to enable specific algorithm variants for specific trait predicates, you're saying "here is the specialised algorithm to use when this predicate is satisfied". Both the predicate and the algorithm code that relies on the predicate being satisfied are part of the template specialization definition. There's no chance of missing a precondition: it's stated right in the definition. Another advantage is that it's completely modular -- you can add or remove specializations at will.
On the other hand, the example in the post using 'if constexpr' wraps the algorithm in an extra layer of indirection (a generic wrapper) with a bunch of constexpr if-else spaghetti used to dispatch to the correct implementation. Even if the if-else spaghetti is pristine, the fact remains that the predicate and the specialized code that depends on the predicate as a precondition have been separated out into two independent code sites.
f( (uint8_t)0 ) will dispatch to the overload f(int) with overloads. The type-trait code will dispatch to f_unsigned(unsigned).