Deducing This
open-std.org
open-std.org
1. The problem statement is well-defined and C++ is the language that cares about these details (compared to, e.g., Java or even Rust). 2. The author correctly identifies a feature in the language that solves the same (or a very similar) problem in a different context with a very hard-to read mechanism invoking special rules. 3. The author proposes additional syntax (essentially necessary for backwards compatibility) that allows to apply the existing mechanism in a new context. 4. The author proposes a new (set of) rule(s) that gives meaning to the new syntax using the existing mechanism.
In sum we have:
* 1 piece of new syntax
* 1 new semantic rule
* offloaded a limited problem to a the ever more complex template instantiation machinery
* less code in implementations, more in headers
Note that the original use case was about how the this argument is passed to methods. But it became a very powerful language extension. (See section 4.2.2) This reduces usability. Imagine a developer calls a method with the wrong const qualifier, yielding an (unexpectedly) const-qualified return type. Currently, there will be a short and helpful error message. With this proposal, this error message will be buried in the template instantiation details.
The problem is that template instantiations have become so generic that you essentially program the compiler with them. But how to generate meaningful error messages remains a mystery. Presumably this will be left to the library authors then.
// Structure with a function `str`, as in the paper
struct StrHolder {
// Assume `str` returns a `const std::string&` when `this` is const
// and `std::string&` otherwise.
template <class Self>
decltype(auto) str(this Self&& self) { ... }
};
// Let's use it here
void main() {
const StrHolder s;
s.str().size(); // OK; calling a const function on `const std::string&`
s.str() = "abc"; // Ill-formed; compilation error here
}
Did I get the scenario right?If so, from what I could tell from this example, I think the error would essentially amount to "can't call operator= on const-qualified `const std::string&`". After that, it would depend on the compiler in terms of how it prints out template "frames". On Visual Studio, it would probably look pretty ugly. On Clang, might be a bit better. I don't recall how G++ prints it out. After the first line, I think it would look mostly like how any template function is expanded, which is... not that pretty.
All in all, I agree it would be a bit uglier than a plain const decorated function, but I don't think that it's bad enough to dismiss the feature, though I admit that this is subjective.
It will also remove the need to of multiple pointless copy pasted overloads with different reference qualification on this.
On the balance I think the change will be an improvement.
Concepts could be used to make the error messages better, but TBH I don't have high expectations on concept error messages (they are still useful for other reasons though).
edit:
> Currently, there will be a short and helpful error message. With this proposal, this error message will be buried in the template instantiation details.
if there is a single overload, there is no point in using this feature. If there are multiple overloads, the error message is already not going to be very nice.
edit2:
In retrospect I think that the language should have had explicit 'this' from the beginning a-la python, with no difference between member functions and free functions (except for virtuals possibly) and '.' should have been just syntactic sugar. IIRC Bjarne thinks the same.
But this perspective is essentially my grievance. C++ turns more and more into an exercise of programming the compiler. usability for people that want to use code is ever more deteriorating. There are more aspects like this in the language, for instance the fact that not even the std library can write out the return types or argument types of many functions. Or the hoops one has to jump through to deconstruct a std::variant (no one dared to add a proper sum type, but it is ok to implement one using the template interpreter).
Concepts do help with the return type issue, at least now you can declare that your inferred return type conforms to some interface; for example:
auto iterator take_n(auto range&& r, size_t n) {...}
take_n is very generic and can return any type. But at least with concepts you can declare that it will look like an iterator. (in practice the result type depends on the range type and take_n declaration will show that).One problem with C++ is that the barrier for adding stuff to the standard library is much lower than adding it to the language. So for example we got std::bind well before we got lambdas; same thing will happen with variants (already in the library), proper union types and patter matching (there are proposals to add both the language).
Bad error messages are also a frustration, but it's not the worst part. In those cases, they can look for examples, read guideline documents (books, really), and play around on compiler explorer to get familiar with idioms.
I've found C++'s templates to be both less strict / less well-defined and in many ways more powerful than Rust's template system. Rust's template system is fully typed and has some important restrictions, the most significant being that it's hard to template on multiple different types and it's hard/impossible to template on integers or other concrete types. C++'s template system is a glorified preprocessor, giving you much power but also an enormous Howitzer foot cannon.
However, rust makes certain design choices that C++ leaves open. They aren't too dissimilar in many aspects, and rust definitely is the sound of the two languages. But what I meant was that in C++ there will always be a very precise distinction between the tiniest nuances of how code behaves because the language wants to allow that level of control.
Take for instance the difference between assignment and construction. Another language might decide that one is explained by the other and be done with it. C++ makes a distinction.
I've written more C++ since C++17 because that in those cases it was the best tool for the job, but, man, every time I do it I feel a strong urge to just burn it all down.
It's an actual programming langauge (rather than a research project) so includes a number of convenience functions and guard rails. Thus 'final' lets you tell the compiler your intention (this is "caring about the details", a kind of guard rail) yet the compiler will already synthesize a bunch of functions for you, either completely (e.g. default constructors) or partially (selecting which template to expand). You can dispute the branch cuts but can't claim that a choice is inherently wrong.
C++ "suffers" because of a very strong (but not absolute) commitment to backward compatibility.
One reason the standard seems bloated is it includes a lot of features that are mostly or even entirely for library writers, as well as a lot of features for system programming not used by most developers. You can write your Java runtime in C++ but you can't write the C++ runtime in Java. They languages are intended for different purposes and in that regard you can't really say one is "better" than the other.
I consider features like this to be in the "reduce programmer opportunity for error" class of improvement, which I personally favor.
I also agree with sibling comment that an alternative title such "Deducing this (A C++ syntax proposal)" would save a click for many readers who aren't interested in C++.
(jk in a sad way though)