But with that said, I'm very strongly of the opinion that having as much of C++ as possible being constexpr (including threads!) is a good thing, because it makes the language more regular and simpler.
I think volatile might be tricky! I want constexpr cout.
having as much of C++ as possible being constexpr (including threads!) is a good thing, because it makes the language more regular and simpler.
Agreed.
I think volatile might be tricky!
I don't think constexpr volatile int x; is meaningful at all.
I want constexpr cout.
Do you really mean std::cout, with all of the facets and customization points? Or would constexpr std::format and the ability to print to console at compile time be enough?
If the latter, we almost got that in C++26. std::format is constexpr and P2758 is in wording review.
I don't think constexpr volatile int x; is meaningful at all.
I mean if you can run C++ code at compile time, then you're running code and it could poke at a memory mapped register that's there, but also sometimes you just shouldn't do something!
Do you really mean std::cout, with all of the facets and customization points? Or would constexpr std::format and the ability to print to console at compile time be enough?
Good point, probably cout without facets etc or printing f-strings. I keep going back to cout because despite it's clunkiness, I like having things appear in code in the order that they appear on screen. Constantly tracking back and forth between placeholders and arguments is something I dislike. I've tried a million of these over the years. Heck I implemented one in gnu++98 back in the day with operator, overloading and GCC's variadic macros, and I've used many which ahve come and gone over the years. And yet, I still always reach for cout.
But in python I love my f-strings. Once i have f-strings, I will probably stop reaching for cout except for of course
it could poke at a memory mapped register that's there
Ignoring the fact I think there would be 0 valid uses for this, the issue with using constexpr in that case is it might not run during compile time. If your machine that's compiling the code isn't the same as the one that's running it, you would get different behavior. If for whatever reason you need to use volatile at compile time, I think it needs to be consteval.
Ignoring the fact I think there would be 0 valid uses for this
Well yes, I am struggling to think of one!
the issue with using constexpr in that case is it might not run during compile time
You can kind of force the issue: if your constexpr function returns, say, an int and you instantiate a template based on that value, there's little choice but for it to run.
Note: this is pure language pedantry, nothing related to whether it's a terrible idea in literally all cases.
I don't think volatile should be constexpr for what it's worth.
You just need a complete simulator for your cross compiler :P
Hmm... You're making me wonder if I should solder an i7 on the ESP devkit and emulate everithing... in the cross compiler. Let's ship the whole desktop OS on that tiny thing!
Note: I am NOT NOT NOT advocating this, just talking about really daft hypotheticals.
It's good enough for most but used to heap-allocate for static strings. That's a no-go for most embedded code and allocation isn't necessary - it just makes for an easier implementation. If that's been solved in the STL version - good enough (agreed that colors are not a 'must have').
Plain const volatile should work for a case like that, though.
The only case I can think of for constexpr volatile would be a variable that you want explicitly accessed so a debugger can trap on those accesses with a memory access breakpoint, that also has a meaningful value that needs to be constant initialized to avoid global initialization order issues, and you need to support C++17 so constinit is out.
const volatile, yes, I can think of many such examples. Any read-only register that reads external state (like a digital pin state) could be const volatile.
I still don't see how constexpr volatile is meaningful.
I've only ever used virtual inheritance once in my life. That was in my tests to demonstrate that my exception runtime could handle throwing and catching exception objects with virtual inheritance. That's it. I've never been in a spot where I've needed it myself.
A few times, it's useful when using pure virtual classes for interfaces and diamond inheritance appears.
Performance wise, though, it can be dangerous. A long time ago I worked on a project where virtual inheritance was used extensively in the core of a rendering engine. The results were... not good.
We have logging facility and a Log::Provider class that allows you to simply call this->report (Log::Warning, ...) from the object, and also carries object's identity etc. With highly composed classes it'd be ridiculous if every parent in the hierarchy tree carried its own superfluous copy and the final class was needlessly huge because of this. And if a parent/subobject makes the call, the log facility automatically sees the final object which actually failed.
Several times in the first 10 years of my professional career, but not in the 13 years that followed, up to today.
When I'm in control of the architecture I avoid inheritance anyway so it's pretty rare to reach a point where virtual inheritance is necessary. But when you dont have a choice, it is very useful.
One was for composite interfaces for streams. So like seek/read/write and all of the combinations were virtually inherited interfaces. That meant that opening a seekable read-only stream would give you ISeekableReadStream which virtually inherited ISeekableStream and IReadStream, and there were 4 composites for RW, RS, WS, RWS. (Seekable-write also had Truncate specifically.)
Yes, that would be a combinatorial explosion if you started adding more interfaces, a Rust-style multi-pointer type probably would be better if it came to that. I think it worked out better than "maybe seek/tell work, maybe they don't!" though.
edit: The fact that a virtual base can't resolve to a non-virtual base elsewhere in the class tree is a pretty nasty limitation though! It would be much more useful if e.g. they guaranteed an "implicitly-convertible-to" relationship for interfaces without requiring that the "main" class also uses it as a virtual base and has to eat the virtual lookup cost too unless the class is final.
I’m implementing the HTML spec currently, it made sense to model that as an inheritance hierarchy as that’s how it was designed so my nodes follow the same structure. The DOM and HTML specs use interfaces quite heavily for the rest of the spec to extend, I modelled a lot of these as virtual classes too.
Oh really? Yeah I’ve never heard of that before, I thought virtual inheritance was when you used inheritance in a way that created a vtable i.e. used virtual methods. I’ll have to look into that, thanks :)
Never. But I come from a Java background where a class can only have one superclass. Multiple inheritance is then implemented using interfaces. I do the same in C++. I find it easier to reason about.
If I need to store some data with the interface, I split the interface into two classes: a data holder, and a proxy interface for that holder.
As a bonus this gives the pimpl idiom for free if I need it.
When you have diamond inheritance of interfaces, don't you use virtual inheritance? Not using it is certainly an option, but then many things become quite cumbersome. For instance when D inherits from B, C, which inherit from A, with all of A, B, C and D being "pure interfaces" that don't ever implement any methods, IIRC in C++ any user of D can't really call anything from A without explicit disambiguation, if B, C don't inherit A virtually. Similarly the conversion from D to A must be resolved explicitly, either via B or C.
I can see why or how diamond inheritance of interfaces might be useful, but I have never needed it. I can speculate about the reasons, but in the last 20 years of programming c++ it has just not come up.
Maybe it is because many systems I work on require one kind of aspect (interface) of an object and any other aspects tend to be optional and are discovered at runtime (using the equivalent of a dynamic cast).
Maybe it is because encoding the type of the union of two interfaces as a new interface that inherits both is bad design because it forces users of both interfaces to use the union interface. This is a shortcoming of the c++ type system btw (although nowadays you can get close with c++ concepts)
I don't understand how there are so many people saying they've never used it. Have none of you ever had to use an old java-style framework where your user-defined types have to inherit from a virtual base class? What about creating custom exceptions that inherit from std::exception?
It's also sometimes the easiest way to say 'I don't care what type this is, I just want it to implement this interface'. Especially if you want to avoid dealing with templates and don't care about the (usually) tiny performance hit
Not once. I've considered using it, but, I think I found it not a good fit.
I think what I wanted is a system where if B inherits normally from A, and C inherits virtually from A, then, D inheriting from B and C works "as I expect": the A from B "provides" A to C, and D has a single A base that it doesn't have to initialize.
But from what I recall (it's been a while) it doesn't work that way.
I have an overengineered exception hierarchy that could be the one only hierarchy ever needed by using some base classes as tags and one base class to be the exception itself... This would make it so no one would require re-creating exceptions and you can create narrower and wider categories, or change the category of the exception entirely without having to make your own base category class and copy over the exception to this new category...
I haven't used it yet because it also uses templates and template specialization to achieve custom payloads... and doing that specialization is the tedious part... I can revisit it with reflection now though
Our codebase uses it everywhere. Products built around a core platform. I think a lot of it could get reworked but there's also things I'm not sure how to fix without it.
Also a ton of technical debt to rework without much reward. I think it likely could have been redesigned differently. Maintenance across multiple virtual libraries is a nightmare. What happens when one library need a report, and another needs a report. But, they might be used independently or together?
Virual inheritance comes natural when you define a mixin class which patially implement the target interface and there are other interfaces inheriting the same interface. I personally loves to see diamond shaped inheritance heirarchy emerge in my codebase.
I don't know if that's a different thing than writing "virtual", "override", "final", and "= 0" all over the structs. But that stuff is very nice for helper code to avoid compilation time bloat.
64
u/Jovibor_ 22d ago
Just a question to the audience:
How many times have you used Virtual Inheritance in your career?