Interviews

Towards a more powerful and simpler C++ with Herb Sutter

Herb Sutter
In this interview, Anastasia talks to Herb Sutter, a chair of the ISO C++ Standards Committee, a software architect at Microsoft, and the author of the C++ Metaclasses proposal.

Blog: Sutter’s Mill
Metaclasses proposal: Metaclasses: Generative C++

A: Hi Herb! Your proposal on C++ Metaclasses is one of the hottest topics in the evolution of the C++ language (and at the end of the interview our readers can find an official video recording of Herb’s CppCon keynote session dedicated to the topic).

H: Thanks, though it’s still a long-term experiment in early stages. I’d emphasize that the real “hottest topics”, new features that are actively being standardized in the near term, are concepts and modules. Those are rightly getting our attention and emphasis in the committee, so I hope people focus much more attention on those. We just added most of the Concepts TS to draft C++20! Modules and coroutines would be very nice major improvements to get into C++20 too, and I hope they may make it. All of those features are important because they help to simplify the way we write (and build) code by helping us write it more directly.

A: For your metaclasses experiment, could you please share how the idea was born? What was the motivation?

H: In a nutshell, the motivation was: C++ is important, it will continue to be important, and so how can we make C++ code simpler by letting programmers express their intent more directly?

Since mid-2015 in particular, I decided to focus my work on seeing if I could find ways to make C++ programs simpler. Because C++ has a long-term future, anything we can do to make C++ programs easier to write, read, and maintain will deliver big benefits to the whole industry.

The way I went about it was by systematically looking at C++ code and seeing where all the boilerplate is – that is, find the things that C++ programmers already do all the time, but that they are forced to express indirectly with excessive ceremony or in complex and brittle ways, and see if we can find a very small number of general mechanisms that could let them express those things in a simpler and more direct and robust way. If we can find the places where we regularly have to fight the language (such as the language’s defaults) or work around the language (such as by using macros or proprietary language extensions) or just rely on English advice (Scott Meyers’ books are great, but wouldn’t it be great if we didn’t need to say 75% of what’s in them), and in those places be able to directly state what we want, that would be actively improving those things that we are doing already.

A “small” historical example is the range-based for loop: We could already write for loops that visit each element, but it took more ceremony such as writing the incrementing logic by hand, and it had more ways to go wrong such as inadvertently incrementing the index or iterator in the body of the loop and skipping elements. By having a ranged-based for in the language, we elevated that very common coding pattern into a language feature that lets us now say directly what we want to do, namely visit each element in order, and that is obviously correct by construction just by looking at the first line of the loop without looking inside the body to see if it might skip elements or something. And I think the range-based for loop, though a “small” feature, was one of the major successes of C++11 – an improvement that people now use every day. Adding it to the language made the language (slightly) bigger, but it made C++ programs simpler.

My question was: Can we do that kind of thing in an even more powerful way at a bigger scale? Can we take entire categories of idioms and boilerplate and workarounds we rely on today, and elevate them into code that can directly say what we mean? I am hopeful that the answer is yes, and that it can at the same time both make the C++ language much more powerful and expressive, but also make new C++ code simpler because we can more often say exactly what we mean.

In the course of that work I found a number of possible candidates that are complementary, and of the ones I’ve found so far, metaclasses is the one that would have the most impact on C++ code (if it works out), so I’m pursuing developing that one first.

A: Wasn’t there another one that came first?

H: Right. Metaclasses is the first “major” piece of that simplification work I’ve brought to the C++ committee, but a smaller piece of it that I brought sooner was the “minor” proposal for the three-way <=> comparison operator (“Consistent Comparisons”). That proposed new <=> operator is known as the “spaceship operator” in other languages that have it already, because it looks like a flying saucer (or Vader’s TIE fighter, take your pick).

I intended to bring forward proposals from my simplification work in an order that started with those that could have the biggest impact and didn’t rely on other pieces, but I proposed spaceship “out of order” because the committee was still actively working on competing and sometimes mutually incompatible approaches to comparisons, and since I had a design that I thought was elegant and sufficiently well-baked, I contributed it. It was a target of opportunity.

Fortunately, the spaceship comparison operator proposal has been well received and has been progressing well. The design has been approved by the committee and, if all continues to go well, it could be adopted into draft C++20 in the next meeting or two.

A: Were there any referenced features in other languages that you looked at?

I always started with existing C++ code, to derive the use cases directly from what C++ programs and programmers already do, and see what handful of general features could improve that code in a future evolved C++. And I worked on that design from the viewpoint of “how I wanted the new C++ code to look” if I were writing it myself, so I worked on that until I had something that satisfied my own taste.

Then, once I had worked out a draft design sketch for a given feature myself, with a focus on how it would look and work in C++, I did also look to see what the experience in other languages was, so we could learn from that, see what worked out in practice and what didn’t. It’s important to keep in mind that every language is different, often in fundamental philosophy, and so what works well in one language may be jarring or not work at all in another, but there are always things we can learn from – both positive and negative. For the <=> operator, for example, I chose that not only because other languages used that spelling, but because I felt that in C++ it should be an operator (for equivalent to <, =, etc.) and because I felt that the symbols <, =, and > connoted that it returned a “less, equal, or greater” result better than any other spelling I tried out – and I’m sure I’m not the first to think so, I assume that other languages’ designers liked that sequence of symbols for the same reason, and I had no trouble agreeing with them and borrowing it. (See the rationale in my P0515 paper.)

And I also appreciate the opportunity to interact directly with some key designers of other languages. For example, my office at Microsoft is three doors down the hall from Mads Torgersen’s. Mads now leads the C# language design team at Microsoft as Anders Hejlsberg’s successor, and it has been invaluable to just be able to regularly bounce these ideas off him, including the metaclasses proposal, and learn what similar or different things his language community is doing. That’s one of many interactions that has been helpful.

A: As C++ is becoming more complex and absorbing more functional and mathematical concepts, are there any fears that a new abstraction level which metaclasses provide will make it even more complicated?

H: Let me distinguish again between the C++ language and C++ programs. I’d like to simplify both, but my emphasis right now is very much on simplifying on the latter – what the developer writes, reads, and maintains.

And adding an abstraction mechanism is the only way to make C++ both more powerful (able to express more) and at the same time make C++ programs simpler. Of course, not just any abstraction mechanism can do that – for every good abstraction mechanism designs, there are hundreds of bad ones that would at best just complicate the language for no gain, or more likely make programs worse. But adding a good abstraction mechanism, if it works out, means that C++ programs can be simpler for the simple reason that they can express their intent directly, because that’s the end result that abstractions are supposed to be for. The more we can enable programmers to express their intent, the more we can do on their behalf.

A: I’ve heard you say the content of the current proposal is not yet final, there are lots of discussions around it, and changes are likely to be introduced. I suppose the final syntax may differ, but what else is being debated? What are the general directions in which the current proposal may evolve from your point of view?

H: I list a few of those in my talk. The major things we’re working on validating right now are to answer: (a) can compile-time performance be fast enough; (b) will usability be good enough, including toolable so that we don’t just write write-only code; and (c) can metaclasses indeed express all the things they are intended to express as listed in the paper, all the way up to being able to replace the currently-proprietary extensions in Qt’s and Windows’ language extensions with portable C++ code. Those will certainly keep us busy for a good year at least just to vet out initial answers to those questions.

Regarding compile-time performance: We know that there is no run-time overhead from the metaclasses feature itself, because it runs purely at compile time. But we need to validate that it’s implementable without inordinate overhead on compile times in real-world compilers and scales to very large C++ code bases. I’m hopeful that we can prove that out, because often metaclasses will be replacing what today is done via other compile-time work that is less direct and therefore probably less efficient than it could be.

For example, today we routinely use template metaprogramming, and that’s inherently inefficient to compile because it’s expressing computation indirectly using a language mechanism that wasn’t designed for that. We should be able to do much better with direct support for compile-time code, such as the constexpr compile-time code blocks proposed by Daveed Vandevoorde and Louis Dionne. The metaclasses proposal assumes that something like that exists, and builds on it.

As another example, today we often compile parts of our code through proprietary language extensions and/or “side compilers,” such as Microsoft C++/CX or Qt moc. If we can bring more of that directly into the C++ compiler, our build chains will be simpler because we will be running through just one compiler, and that should immediately get the benefit of not having to read and parse the same code twice. We also hope that it should be more efficient to do directly in C++ compile-time code the things those side compilers do today as a separately bolted-on products. But we can’t assume those things, we have to implement and try and measure, and that’s why we’re continuing to actively prototype and measure the proposal, starting with smaller examples and smaller code sizes and gradually building up.

Besides performance, a key goal I have always had for metaclasses is that they be expressive enough so that they can express in C++ itself those things that today need to resort to expressing in proprietary language extensions like those in COM IDL or more recently C++/CX (I led that language extensions design as well as C++/CLI, so I feel that pain well) and Qt’s macros and extensions that are understood by their separate moc compiler.

Please see section 4 of my paper for these goals and examples.

I’ve often said that part of my goal with metaclasses is that I never want to have to design C++/CLI or C++/CX again. Interestingly, in response to that I’ve noticed some blog and Twitter comments along the lines of, “yeah! finally you don’t want C++/CX and will stick to C++ itself!” where the commenters seem to think I’ve seen the light and repented of those language extensions. That’s not the point at all – I’ve always wanted to do everything in pure standard C++ itself, but the point is that we couldn’t. Today’s C++ can’t express everything that’s needed, and that’s why COM and CORBA and Qt and others needed to create those language extensions – so extensions to express that additional information were necessary, and the additional information had to go somewhere, either as in-language extensions or as an equivalent out-of-language IDL (interface definition language) style companion file. So what I’m actually trying to do now is make it so that the language itself can express those things without leaving the language, but that requires having a more powerful C++ language than we have today. C++17 still can’t express those things sufficiently; I’m hopeful that a not-too-far-future evolved C++ will be able to do so.

A: You’ve presented the proposal to the C++ committee at the summer meeting. What was the reaction like? What kind of feedback did you get? Was there anything unexpected or anything that led you to change your mind on some points?

H: The reaction was very warm, which was appreciated; it is after all a big feature. However, that said, it’s early days and it’s easy to vote that, yes, someone should go and do more work!

The committee also gave specific feedback on potential changes they would like us to explore, and that is to avoid the “modify-in-place” semantics of the code in the current paper. For example, in the original proposal, the “interface” example looks roughly like this, where you write the code with the mindset “I’m inside the class the user wrote in source code, and I’m looking at my members and making changes as I go, and both the source and destination are called ‘interface’”:

$class has_virtual_destructor { virtual ~has_virtual_destructor() noexcept {} };
$class interface : has_virtual_destructor {
constexpr {
    	compiler.require($interface.variables().empty(), "interfaces may not contain data");
    	for (auto f : $interface.functions()) {
        	compiler.require(!f.is_copy() && !f.is_move(),
            	"interfaces may not copy or move; consider a virtual clone()");
       	 if (!f.has_access()) f.make_public();
        	compiler.require(f.is_public(),
            	"interface functions must be public");
        	f.make_pure_virtual();
    	}
}
};

The committee felt that the syntax should make it clearer that the class the user wrote in source code is a read-only input, and that the definition(s) we generate from that are then also read-only, and it would help to have different names for those things. So one option would be to distinguish the first as a “prototype” type and use the metaclass name, here “interface,” to refer to the final generated type, which we inject things into explicitly (let’s use the strawman syntax -> for that injection):

$class has_virtual_destructor { virtual ~has_virtual_destructor() noexcept {} };
$class interface : has_virtual_destructor {
constexpr {
    	compiler.require($prototype.variables().empty(), "interfaces may not contain data");
    	for (auto f : $prototype.functions()) {
        	compiler.require(!f.is_copy() && !f.is_move(),
                "interfaces may not copy or move; consider a virtual clone()");
        	if (!f.has_access()) f.make_public();
        	compiler.require(f.is_public(),
            	"interface functions must be public");
        	f.make_pure_virtual();
    		->f;
    	}
}
};

So the code is structurally the same, but the second form is clearer that “prototype” is a read-only input and that we are generating a separate definition, which once generated will also be read-only as all C++ definitions are. So this doesn’t affect the basic design, but more the shape of the steering wheel.

See sections 5 and 6 in my current proposal paper for examples of those and another design point where we’re exploring options and variations, with additional examples and alternatives.

A: What are your expectations for Metaclasses in C++? Do you think we’ll get them in C++23?

H: Oh, it far too early to say anything like that. The only metaclass milestones I’m paying attention to right now are validating the basic “will it work and achieve its goals” questions: Are the compile times sufficient (not significantly worse, and ideally maybe sometimes even better, compared to today’s techniques)? Can it actually express all the things that Qt and C++/CX need language extensions and side/extended compilers to express, that we can’t do today in portable C++? If the answers to those don’t bear out, it’ll never see any C++ standard at all. But if those answers do bear out over the coming year or maybe more, then we’ll know we’re on a good path and can start having a conversation about the next set of steps to follow.

A: You’ve got the Clang implementation ready and demonstrated it during your talks. How about a Visual C++ compiler? What do your colleagues at Microsoft think about the proposal?

H: First, I wouldn’t call the Clang implementation “ready” – it’s a prototype in progress, with great thanks to Andrew Sutton who is doing that implementation as well as to Matt Godbolt who is hosting a copy on his wonderful site. That partial prototype currently compiles a handful of the examples in the paper, and is still being extended to support more examples, and is also being reworked as needed along the way to make the compile time work as efficient as possible as we scale up to bigger examples. It will continue to improve.

The important point is that “metaclasses” relies on three other features that C++ is likely to end up getting anyway, and nearly all of the work is in those features. These four pieces are called out in my talk and in the paper, especially in section 7: reflection, compile-time code (e.g., constexpr{} blocks), injection, and then metaclasses that builds on the other three. All of those are proposals working their way through standardization, in roughly that order – reflection is the most advanced. And I expect compilers will implement them in that order as well.

“Metaclasses” by itself assumes the other three exist, and from early rough work estimates I’ve seen so far in multiple major compilers, I’d say at least 80% of the total work is in the first three pieces. Again, those are three features that I think it’s very likely C++ will get eventually in some fashion on their own merits, independently of how metaclasses work out.

A: In your keynotes at CppCon 2017, you talked about how important it is that language features be toolable. Can you provide a couple of examples? Do you think the current language features (the ones we recently got in C++17, or the ones coming with C++20) are toolable from your perspective?

H: One example is compile-time code. We may not have constexpr blocks yet in C++17, but we sure have already have been getting more and more constexpr functions with each release of C++ since C++11. Today, nearly no mainstream tools provide a good “step into” for one of those that actually runs at compile time, as we would expect for a normal function. So that’s a need that is already there, and whatever we do for constexpr functions will also naturally extend to work well with other compile-time code blocks we may add in the future.

A: Talking about tooling and Metaclasses support in IDEs, what features do you expect from such tools? What could be beneficial for those who start using Metaclasses when they arrive to the language, or at least get all major compilers implemented?

H: Definitely see section 7 of my latest paper, linked above, that just appeared a week ago for our next ISO C++ meeting. It talks about tooling, and in particular it highlights that when you break the work down into reflection, compile-time code, injection, and metaclasses, virtually all of the tooling work arises in the first three – and then I expect the tooling we want for those would just require incremental extension for metaclasses. So metaclasses themselves don’t create a big tooling requirement – compile-time code and injection in particular do create that. Whatever tooling we provide for compile-time code and injection will naturally work well with metaclasses which after all just builds on top of those features.

I make the same point in the middle of my CppCon talk as well, but see section 7 in the paper which overlaps with that but includes a few more details.

A: Thanks, Herb!

Here is an official video recording of Herb’s CppCon 2017 keynote session:
https://www.youtube.com/watch?v=4AfRAVcThyA

image description

Discover more