Scott Meyer's articles (and numerous video presentations) on Universal References are educational but he doesn't make it clear to the audiences the bigger picture of where and why they'd need to really learn it.
The Forwarding Reference (aka Universal Reference) is for programmers writing generic templates. A lot of C++ programmers can do a ton of productive work and get by for years without writing any templates from scratch. Yes, they use others' templates (such as STL/Boost libraries) but they don't necessarily write their own. I have no survey data but I'll take a wild guess that less than 1% of C++ programmers need to memorize template Forward References.
As an analogy using the plain C Language, it's as if someone created very prominent blogs about the complexity of "char ∗(∗(∗a[])())()" with long paragraphs explaining how it's "an array of pointers to functions returning pointer to function returning pointer to char".
If that type of essay had a lot of visibility, people might think that "Wow the C Language is difficult and too complicated!" In reality, a lot of C programmers don't write complex declarations like that. The more common pointer-to-function for something like qsort() is very easy to write.
"char ∗(∗(∗a[])())()" may have entertainment value in an obfuscated C contest, but it is otherwise useless.
Using && in templates may be advanced C++, but it is both useful and mainstream.
You're essentially saying, "don't worry, no one uses the new language features anyway". But that's not a convincing response to a complaint about the incredible complexity introduced by a particular new language feature.
>You're essentially saying, "don't worry, no one uses the new language features anyway".
No, I'm not saying that. A lot of C++ programmers are application programmers who glue together Qt GUI with some code to read a TCPIP socket or file and write out some data. Many C++ programmers simply don't have the day-to-day need to write generic templates (libraries).
Yes, it may be useful and mainstream to you but I don't think that is reflective of the whole C++ programming community at large.
>But that's not a convincing response to a complaint about the incredible complexity introduced by a particular new language feature.
When I studied it, the "&&" syntax and type deduction rules seem to be "irreducible complexity". Every computer science problem that involves one set of syntax to write another syntax (meta template programming) is complicated. This includes XSLT to rewrite XML, or PHP code that dynamically writes javascript syntax embedded inside of HTML syntax. Reading any of that code-that-writes-code is always ugly. If we were to challenge a language designer to recreate a syntax that meets the same exact goals of C++ (perfectly pass parameters without casts, no unnecessary copies that kills performance, with static type checks, etc), he'd eventually end up at the same complexity you have now. In other words, there is no breakthrough A.I. compiler algorithm that lets the parser correctly infer how parameters pass through to generic code. If there's a whitepaper that claims otherwise, I'd love to read it. (Rust generics are simpler but they also do less than C++ templates.) The TLDR is that C++ templates is a code gen tool and like any other nontrivial code gen tool, they end up being complicated.
>Every computer science problem that involves one set of syntax to write another syntax (meta template programming) is complicated.
I'm not sure about 'meta' part you mentioning, but it seems that all the complexity of c++ templates comes from declarativity of these. If they were imperative — e.g. 'hey, compiler, put an additional if into specialization for that particular type', or produce specializations in a loop, or create template generators, etc — that would feel less bizarre. Deep inspection of seemingly simple terralang.org turned that upside down to me. These two languages (luajit/terra) are both not very popular, but the idea of simple imperative codegen is right there. I wish I were able to replace terra part with just C...
>When I studied it, the "&&" syntax and type deduction rules seem to be "irreducible complexity".
That may well be, given a particular set of constraints. But that is exactly the issue with C++ today. It's a conglomerate of powerful and diverse features, and so everything that gets added interacts in countless ways with everything that's already there. There's no orthogonality at all. Everything is both irreducibly and impossibly complex at the same time.
You're right that meta programming is inherently more complex than other issues. But it's not the only area where C++'s complexity is outgrowing our (or at least my) cognitive capacity if the goal is to write reasonably reliable code in a reasonably productive way.
" A lot of C++ programmers can do a ton of productive work and get by for years without writing any templates from scratch" as well as people who choose not to have a stove or oven in their kitchen can eat, but with the delusion that they are smartly avoiding difficulties.
My personal main problem is things like you can't template on an r-value reference, as T&& in a template matches both r-value and l-value references and then you need std::forward or std::move and then I start getting confused.
I wish in templates they had gone with the suggestion I saw to use T&&& to mean "r-value or l-value reference", and then let T&& just mean "r-value reference".
If you want to template on an r-value reference, the easiest way is probably to use a forwarding reference and disable the template if the template parameter deduces to a reference
template <class T, class = enable_if_t<!is_reference<T>::value>>
auto f(T&& x);
First, "class = enable_if_t<!is_reference<T>::value>" is just "class Dummy = enable_if_t<!is_reference<T>::value>" that doesn't bother to name Dummy. It's providing a default template argument for a function template (this is a C++11 feature; in C++03, only class templates could have this).
Next, "enable_if_t<!is_reference<T>::value>" is a C++14 library feature (enable_if_t is an alias template; the Core Language gained alias templates in C++11, the library gained these in C++14). It is a synonym for
"typename enable_if<!is_reference<T>::value>::type", which is more verbose (and what one had to type in C++11).
"is_reference<T>::value" is a C++11 type trait, that is true when T is X& or X&&, and false otherwise. (It's implemented with partial specializations, and a "static const bool value = true;" data member - actually constexpr these days, but no difference there. This is type traits 101.)
enable_if is a library helper for SFINAE. (Not necessarily C++11 Expression SFINAE; here, just C++03 classic SFINAE is being used.) Basically, enable_if<true>::type is void. enable_if<false>::type doesn't exist. This works with the Core Language's SFINAE rules to make function templates vanish from the overload set (if you don't know what SFINAE is, look it up; it's a little complicated, but it exists for good reasons, and it goes back to C++98).
So, this is how one writes a function template that vanishes under certain criteria. This is often desirable compared to doing different things for different types (that can often be done with "tag dispatch") or emitting compiler errors for certain types (that's a static_assert).
class = X is just an anonymous template type parameter with default value X, just like
auto foo(int = 0);
declares a function with a int parameter with default value 0. In this case, it's just a conventional place to stick an expression in the declaration to trigger SFINAE. If you don't need SFINAE, you can also just place the condition in a static_assert in the definition
template <class T>
auto foo(T&&) {
static_assert(!is_reference<T>::value, "call me with an r-value");
...
}
Move semantics have been great, in my experience (enabling things like unique_ptr). What is causing issues here?