Rendered at 23:51:16 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
amluto 2 days ago [-]
I have a little class called EImpl that is kind of like std::indirect except that it embeds the impl instead of pointing to it. It takes three template parameters: an embedded struct, a size and an alignment. It static_asserts that the embedded struct fits in the size and alignment, and it embeds it with approximately zero overhead. It’s about as easy to use as any other pImpl technique.
Do you have the code shared somewhere, via a blog post or code snippet somewhere?
22 hours ago [-]
knorker 2 days ago [-]
But if the pimpl size grows too big, you're forced to break ABI?
And before it grows too big, it wastes memory. For your use cases it may not matter, and the saved pointer indirection may be more important, but maybe the person who has a million item vector of objects doesn't appreciate a 300% "just in case" memory overhead. The overhead may also hurt cache hits.
If you're doing this to save the pointer indirection, you should benchmark it for every use case, since negative cache effects may dwarf that gain.
Then again, extra padding can also help performance, for some workloads (especially multi threaded read/write against a vector of objects).
So without further context, there's no way to say if your way hurts or helps. It's certainly not a general solution.
feelamee 12 hours ago [-]
> And before it grows too big, it wastes memory
how it wastes the memory? It's just a bytes array of the same size as struct.
I would be more imposed on how idiomatic pimpl with heap allocation influences memory usage and cache hits
amluto 1 days ago [-]
I use it in a monorepo to break compile-time dependencies. If I need to adjust the size, I spend a minute or two rebuilding.
knorker 1 days ago [-]
Yup, agree that in some cases this fixes it. Indeed, in another comment[1] on this comment branch I said "Not everyone works at Google and builds all binaries from scratch from a monorepo every time".
So you're only trying to solve compile time issues (incremental and not)? Maybe the right long term solution is C++ modules, instead? And maybe "just" a matter of having your build environment support modules?
"Breaking ABI" isn't an issue unless you can't compile your code anymore. It's pathetic that C++ has been so hamstrung over ABI that we're willing to stop improving.
bluGill 1 days ago [-]
Unfortunately I have some customers of my library that won't recompile. Hard to blame them because it is safety critical and needs recertification. In just glad the certification process is willing to not recertify my code when I change it
pjmlp 16 hours ago [-]
Because outside Linux and BSD distros, the large majority of C and C++ developers care about binary libraries, and companies do make a business out of it.
So regardless of what WG14 and WG21 do, compiler vendors will ignore them, if it means angry customers.
In design by committee languages, new standards are only relevant to the extent implementers actually care about them.
knorker 1 days ago [-]
Not everyone works at Google and builds all binaries from scratch from a monorepo every time. And even then, maybe even Google doesn't rebuild libstdc++ as part of this.
GNURadio consistently uses pimpl for blocks, as I understand it mainly for ABI.
> willing to stop improving.
I think that dismissing it like that shows a naive understanding of execution environments, binary interface design, and in general systems software engineering.
StilesCrisis 9 hours ago [-]
If you want a fixed build environment, pick a toolchain and stick with it.
If you want the latest and greatest, a willingness to rebuild your code seems like a reasonable prerequisite to me.
If you need a binary blob that can withstand toolchain versioning, use a dynamically linked library.
ryani 2 days ago [-]
Sadly, you can't easily do the full pimpl idiom in C++.
The pimpl idiom is a C idiom where a header declares an opaque structure and prototypes of functions that take pointers to that structure. In C the OP example would look something like
// widget.h
typedef struct Widget_t Widget; /* opaque! */
Widget* Widget_Create(const string* pName);
Widget* Widget_Clone(Widget*);
void Widget_Destroy(Widget*);
void Widget_click(Widget*);
int Widget_clickCount(const Widget*);
const string* Widget_label(const Widget*);
// widget.c
struct Widget_t {
int clicks;
string *name;
};
// ... implementations of the functions from the .h ...
In particular, in C `Widget` directly has `clicks` and `name` as fields.
But in c++ we like to use methods on objects, and in order to do this, you need the class declaration in scope, which means your current compilation unit needs to have seen all of Widget's data members. In practice this means if you try to use "pimpl" in C++, you do something like the OP where there is a pointer to an opaque type inside your class.
However, this is not the same thing. Methods are called with a `this` pointer, which means every access to the internal structure adds a second pointer dereference. This is why this isn't the true pimpl -- it wastes an extra deref on every access.
You can get true pimpl in current C++ but it's a lot of boilerplate and heavily relies on compiler inlining. An implementation of the example from the OP: https://godbolt.org/z/6EznxeG1n . In practice this is too much work, hard to read, and so nobody does it.
For the c++ standards committee: please add an "opaque class" feature where the class can only define non-virtual method prototypes. Then the full class declaration, in the associated cpp file, could include its parent classes, actual data layout, and function implementations.
leni536 1 days ago [-]
A similar opaque pointer pattern with member functions is possible to do with inheritance.
dooglius 1 days ago [-]
Completely agreed, but in fairness to the c++ standards committee, this is solved from a standards perspective by c++ modules
Panzerschrek 2 days ago [-]
> Never null: it always holds a value, except in the moved-from state
I am wondering why C++ can't implement "non-null" unique_ptr version in the same way? As I know, that the main argument against implementing it is, that it's can't be done, since move-out unique_ptr still can be null.
feelamee 12 hours ago [-]
I think nothing prevents this, but this is just not the point of unique_ptr. unique_ptr is still ptr, so it consequently follows raw pointer semantics.
What do you mean by move-out unique_ptr? That the not_null ptr type would ne null after it's been moved?
In that case that's just a plain usage error, same as how you could memset it to null.
knorker 1 days ago [-]
I've been thinking about things like that a bit. I see a very useful and safe Rust pattern, and wonder if I can possibly implement it in C++. Mostly the answer is no, because C++ is too powerful.
I would love to proved wrong, but everything I can think of still leaves a footgun that's easy to trigger by accident, and thus negates the point of the solution.
I think the can't-reference-after-moved-from and objects-are-not-Copy-by-default are key to creating these types (at least enforced at compile time). And that would require major language changes, at least as big as the C++11 changes.
pjmlp 1 days ago [-]
Static analysis is the answer for some of these questions.
While many of us that like C++, would wish for a different evolution process, some of this stuff can be enforced by static analysis tooling.
Just like despite being safer than C++, we still use Sonar, FindBugs, FxCop/Roslyn Analysers, go vet, rust clippy, one more reason to actually use Sonar, clang-tidy, PVS, MSVC analyse,... with languages like C and C++.
In some of these you can add your own rules even, even if not always that straightforward.
zabzonk 2 days ago [-]
Hmm. Do people use PIMPL that much (I have used it, but rarely) that we need std library support (and testing, documentation, understanding)? Just asking.
dvratil 2 days ago [-]
It's often used in libraries where you need to guarantee ABI compatibility. Fixing a bug or implementing a feature may require adding a new member into the class, which would change its size (thus break ABI compatibility). PIMPL is the typical solution here, since the inner/impl class is not part of the public ABI.
I also like to use it sometimes to "hide" private methods and their documentation into PIMPL, so the public header is kept clean.
hasley 1 days ago [-]
Yes, it is a good fit when a customer is supposed to use some functionality one has implemented but shall not see the implementation.
zabzonk 2 days ago [-]
> PIMPL is the typical solution here, since the inner/impl class is not part of the public ABI.
Yep, that's what I've used it for. Didn't find it too difficult to implement it myself, but I guess every bit of convenience/bug avoidance helps.
flohofwoe 2 days ago [-]
This std::indirect thingie looks more like a general helper for any data 'dangling off' an object, not limited to pimpl.
Not sure how much pimpl is used in reality, but it's a pretty ok solution to speed up build times (apart from unity builds), because it avoids having to include headers that are only needed for the private state into the public interface header.
daemin 15 hours ago [-]
It was used extensively at a former workplace of mine where each class that wasn't a message or data type was a pimpl. They had implemented their own private pointer class to handle it. It worked well enough to avoid pulling in lots of headers, but was still a PITA when you wanted to change methods as you'd always need to change the method signature in at least 3 places - header file declaration, source file definition, source file impl definition.
neonz80 2 days ago [-]
They didn't add PImpl support, they added std::indirect which can be used for PImpl among other things.
feverzsj 2 days ago [-]
Yes, if you actually care compile times.
RossBencina 2 days ago [-]
Indeed. I primarily used PIMPL when I want to avoid polluting public header files with implementation detail #includes in cases where forward declarations are impossible or unwieldy and inline methods are irrelevant.
einpoklum 2 days ago [-]
My approach to reducing the compile time of code which uses a class is moving the functionality out of the class and into standalone functions; or at least moving the method definitions into a non-header `.cpp` file.
otabdeveloper4 2 days ago [-]
Lucky for you, I don't.
green7ea 2 days ago [-]
I remember using it all the time for the Windows headers because they pollutes the compilation unit like you wouldn't believe — the rule was to only include them in c/cpp files.
In precompiled headers to solve that particular problem.
silon42 2 days ago [-]
That's kind of a hack, still best only used in implementation files, not headers.
whizzter 2 days ago [-]
The irony is that including/using many standard c++ headers is far far more expensive than including a lean windows.h these days.
To make hobby-coding fun, i use a mstdp.hpp that implements "naive" versions of unique,shared,function,etc that compiles faster than including just one of the std versions (and yes, MSVC versions of those libraries seem to be excessivly complex).
pjmlp 16 hours ago [-]
Not when using modules, which I have moved into on hobby projects.
The import std is much faster than plain #include<iostream>.
maccard 14 hours ago [-]
This is a fair point. Last time I tried modules in anger, it wasn't viable. Cmake didn't support them (well, they were experimental), intellisense didn't work and there were many ICE's in MSVC.
Maybe it's time to move my hobby project over and see how well it works.
pjmlp 14 hours ago [-]
I give you that Intelisense is broken as always, and the best experience appears to be with Clion, but I can live with it.
Best experience is VC++ with MSBuild, cmake/ninja work great with latest clang however import std support is not yet enabled by default in CMake.
I only care about VC++ for hobby coding, hence using modules.
I've just spent an hour trying to set up import std to use std::print on MacOS. I got there, eventually with cmake + ninja. I hit an absolutely ludicrious number of errors for effectively a hello world, and I don't understand why in 2026 it is as complicated as it is.
But, no ICEs and it's working! I'll start writing some code with them tomorrow.
maccard 1 days ago [-]
That may be true, but it's a "hack" that has caused me approximately 1 issue in 15 years of writing C++ professionally, and that issue was a problem with our build system not the precompiled header.
> Still best only used in implementation files, not headers.
We only compile implementation files!
seanhunter 2 days ago [-]
Back when I used to write C++ it was used all over the place. Admittedly that was a log time ago.
jeffreygoesto 2 days ago [-]
How you doin' fellow Qt-kids? ;)
lyorig 15 hours ago [-]
Small nitpick, but a post about C++26 should really not be using `const std::string&` parameters. We have had `std::string_view` for this exact purpose since C++17.
3form 2 days ago [-]
This looks great indeed - I wonder if there are any particular gotchas, though, as things often are in C++next land.
With many of the features coming into the language over time, I kinda wish that a bit more restricted subset of it eventually becomes a thing, but I know in practice it might as well be a completely different language. That, and I expect that still many other things have not been resolved as well as they are elsewhere, such as build system and dependency management (although I haven't touched this stack for a while now, so I would love to be surprised).
torginus 1 days ago [-]
The gotcha is that this is a 90s C pattern, and software that actually needed this has been written for 3 decades by now
HarHarVeryFunny 1 days ago [-]
Sure, but this interface/implementation split is such a common pattern that it would be nice if the language handled it implicitly (NOT this std::indirect solution) rather than forcing the developer to use some explicit pimpl/handle pointer.
You'd essentially like to derive a class/module implementation from it's corresponding class/module interface (which is all the user sees), but have the language automatically add a hidden "pimpl" pointer to the interface class. The implementation would then essentially use "this" to access public members, and "pimpl" for private members.
dingaling911 2 days ago [-]
"Holds a value, except sometimes"
whizzter 2 days ago [-]
Where are we with modules, isn't pimpl there largely to avoid costs related to including the world?
I was pondering on why he was putting the defaulted methods in the cpp, any particular reasons?
I did realize that the indirect version is required to be in the cpp since the header won't know how to copy without knowing the definition of the impl class.
pjmlp 1 days ago [-]
Even with modules, if you expose such types over the ABI, naturally the machine code memory layout will change, this is an issue regardless of the language.
ghosty141 2 days ago [-]
pimpl helps more since its trivially implementable in existing codebases while modules are a much bigger pain.
cemdervis 2 days ago [-]
pimpl also helps to keep data structure layout stable, e.g. Qt's d-pointer convention
binary132 1 days ago [-]
The article explains why, indirect and unique_ptr are templated and require the complete definition of the type, and the default impls of those methods use methods of the templated types.
knorker 2 days ago [-]
pimpl also makes it much easier to make changes without breaking ABI. E.g. shared libraries.
Surac 11 hours ago [-]
The only thing i do with pimpls is pop them :)
einpoklum 2 days ago [-]
The example is problematic, in that:
1. click() should not be a member of the widget. A widget does not click; a user clicks a widget. A click can change a widget's state, but the state might change because of other effects, e.g. pressing a key when the widget is focused. But then, that's just one of the issues with treating UI widgets this way.
2. More to the point - clickCount. If this is a button, it shouldn't keep a record, or aggregate, of its clicks within it; and if it's a widget where this does really matter, like a range control where more clicks mean a value that goes farther along the range - you still would not keep the count of clicks, but the current position. Statistics about the interaction with an object should not be part of the object itself. At most it might be legitimate to have, say, a Widget class, a template like <class Stats> StatisticsTracker , and then class TrackedWidget which uses that as a mixin, i.e. inheriting both Widget and StatisticsTracker<ClickStats>. And that's already stretching it beyond what I would find reasonable.
3. Having something named is another aspect of objects which may be a good fit for a mixin class.
Anyway, an 'indirect' type for objects you don't know the definition of sounds nice.
A few more nitpickis about the example:
1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
class Widget
{
public:
void click();
int clickCount() const;
std::string label() const;
private:
struct Impl;
std::indirect<Impl> pimpl_;
};
and that's the beauty of the rule of 0.
2. Why return an std::string for the label? The label() method should return an std::string_view
spacechild1 2 days ago [-]
This is just a simple example, therefore nitpicking on the semantics of the Widget methods is a bit silly.
> 1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
The blog post explicitly explains why this doesn't work. You have to define these methods in the source file because they need to see the definition of the Impl struct.
murderfs 2 days ago [-]
> 2. Why return an std::string for the label? The label() method should return an std::string_view
This only works if it's always the same value. This doesn't work if the label is for example, set to `std::to_string(clickCount())`
1 days ago [-]
tempodox 1 days ago [-]
This is another great enhancement that makes me look forward to C++26.
z0ltan 2 days ago [-]
[dead]
Yomguithereal 2 days ago [-]
[flagged]
smallstepforman 2 days ago [-]
Oh god, what monstrocity have we created?!?
All this complexity follows unique_ptr and copy constructor madness.
Anything with pointers with ownership should never be copied - period. Reference pointers - OK if scope/lifetime is known.
Can we have c++11 lite?
pjmlp 16 hours ago [-]
Yes, configure clang-tidy as such.
Maxatar 1 days ago [-]
So data structures like std::vector should never be copied - period?
HarHarVeryFunny 1 days ago [-]
I think the parent is just referring to pointer types, not anything (like std::vector) that may use pointers internally.
This "std::indirect" tries to have value semantics, but in fact it's just another type of smart pointer and uses pointer syntax (pimpl->foo : forced since C++ allows "->" as a user defined operator name, but not ".").
But it's a weird sort of "pointer" given this copying behavior, which is maybe why they didn't give it a "_ptr" name.
shevy-java 2 days ago [-]
C++ is getting more and more complex. It used to be said that people use only a small percentage of it when writing C++, but I am beginning to think that the cake is a lie here.
pjmlp 2 days ago [-]
Besides being a common idiom, for how many warts C++ might have, no one is rewriting LLVM, GCC, V8, JVM/ART and .NET runtimes, CUDA/Metal/DirectX, Unreal, Godot,.... into something else, RIR is not happening there.
People will contend themselves with "C++ the good parts", helped by clang-tidy, PVS, MSVC analyse, and move on.
feelamee 2 days ago [-]
where "more and more complex" do u see in this article? This is a basic C++ idiom, which constantly used by developers
konstmonst 2 days ago [-]
std::indirect looks for me like another pointless c++ thing that already works with forward pointer declaration. You can add it to another ton of pointless things C++ adds without fixing the old ones. The issue with c++ is that it is so big, that everyone uses some kind of dialect of it and the fancier it gets, the less readable it becomes and the more magic happens behind the curtains.
A developer of a C++ codebase now has to learn a specific meta language of this codebase. Fuck that, I have enough languages and their idiosynchronies to remember for my work now. After using Go for a pair of years returning to C++ is like coming back to a big archaic mess. I'll just go learn Rust instead and forget all those new useless C++ templates like std::indirect
wwind123 2 days ago [-]
I think it's kind of awkward either way. The standard committee keeps adding new features to the language to address common pain points in the industry. But many people don't have that much time to learn the new features, and hates it when seeing something in the code but can't intuitively understand what it's doing. I once witnessed a 10+ year C++ coder (that had been immersed in some old C++ code base for many years) seeing a piece of C++14 code for the first time -- he said it reads like an entirely different language, not the C++ he's familiar with at all.
pjmlp 16 hours ago [-]
Same to other languages, e.g. someone stuck in Java 8, will be lost in modern Java 26 code.
einpoklum 2 days ago [-]
> But many people don't have that much time to learn the new features
Because they spend so much of their time struggling with the pain points of the older code.
> but can't intuitively understand what it's doing
For (most?) new vocabulary types, it is rather intuitive to understand what they do. optional, variant, indirect - you may not remember the details by heart immediately, but you get the general idea and expect that they would behave in some reasonable way. And mostly, they do. That's not to say they're perfect: I feel like vomiting looking at std::variant's and how you have to work with them, as opposed to a proper case classes / algebraic union types in the language itself. And yet - when someone puts one in their class, instead of a bunch of code in a bunch of methods, you know what's going on. It does "read like a different language" somewhat, and that's good. The nicer language has been struggling to get out, as the saying goes.
jstimpfle 2 days ago [-]
Pretty much all the C++ features I've used are good enough to write some toy code snippet, but it's hard to use them to good effect at scale without causing massive problems.
Even classes are an instance of this, they were to solve some perceived problems, but they created much bigger issues, such as readability issues and introducing many more compile time dependencies.
PIMPL wasn't even a C++ feature but an idiom pushed by some people. It is next to unusable because you have to duplicate the API and write all the call forwards.
One problem with std::unique_ptr for example is that there is no ergonomic way to use it to hide implementations. The reason is it relies on destructors and to use destructors the class definition needs to be visible.
feelamee 24 hours ago [-]
> std::indirect looks for me like another pointless c++ thing that already works with forward pointer declaration. You can add it to another ton of pointless things C++ adds without fixing the old ones.
For me your comment seems pointless, because std::indirect have a precise and clear problem which it solves. And this is totaly not a "hackery fix of language drawback". In this case language works as intended and std::indirect just close the gap for facility which is still would be written by hands, if there is no std::indirect.
kzrdude 1 days ago [-]
Part of the problem is that features are not added fully
This here should not need to happen:
> document that moved-from objects cannot be used
It's moved from, so of course it cannot be used. Shouldn't have to add an assertion in every method to guard a fundamental invariant.
dvratil 2 days ago [-]
I think the parent's point is that we started with raw pointers to implement PIMPL, then we had std::unique_ptr, and now we have std::indirect. So there are now three different ways how PIMPL can be implemented, each has its gotcha's and subtle differences that one needs to keep in mind. In large codebases you will now have to deal with all three solutions being used, depending on how old the code is.
feelamee 1 days ago [-]
I think point was another.
And even if u are right - this is a silly point. In any general language you have enormous amount of ways to write something. I'm at least implemented pimpl in two different ways (despite lot of little quircks): classic pimpl with heap allocated implementation and fastpimpl which accepts size as compile time argument and create implementation directly in local stack buffer.
> In large codebases you will now have to deal with all three solutions being used, depending on how old the code is.
this is really a problem of "large codebases" and not C++ complexity. If people writing badly organized code.. than.. nothing will help them. Even such string language as Rust.
---
But, I go aside.
I can be wrong here, but this is how I see this:
- author author worked with C++ early in his career
- now his work with something like python or javascript or "promptscript" or don't programming at all
- he managed to taste C++ drawbacks
- now he reading such article just to see "what's new" in technology he was interested in the past.
- he see something what he don't understand from first sight
- this looks like a complex and quirky thing
=> he decide that problem in the language, not in his incompetence in this area
I see such comments very often and usually people don't provide any technical expertise. But just their feeling that "oh, this thing was so complex, now it even more complex".
daemin 14 hours ago [-]
The article's point of view is that using unique_ptr to implement a PIMPL idiom is inadequate since it doesn't allow copying of the outer type without implementing your own dedicated operators. Hence the new type which does act more like a value, leaving the decision to have the outer class move-only or copyable up to that class, without needing to write any special code.
gblargg 2 days ago [-]
The point of each improvement is fewer easily-made errors. Having implicit deep copying handled avoids lots of errors with manually implementing it the oldest way.
jstimpfle 2 days ago [-]
Well that's a lie. I've long been back to raw pointers and it's by far the easiest way to do it. All of Pimpl, unique_ptr, and whatever other clever mechanism (I'm not even looking at std::indirect anymore) just aren't really ergonomic.
Nobody needs "deep copying", ever. It's not even well defined what it should mean (i.e. how deep etc.). It's purely a theoretical problem with no good practical (one-fits-all) solution. The only practical way is to copy what you need copied, when you need it. Done.
gblargg 19 hours ago [-]
The whole point of pimpl is to store additional per-object state in a separate object (to reduce header dependencies). So it should act just like normal member variables for standard constructors and assignment.
bluGill 1 days ago [-]
Easiest way to make a silly mistake and have your program break from memory safety errors.
jstimpfle 1 days ago [-]
Well, the best way to prevent mistakes is to make everything super complicated and damn hard to do. The best way to prevent mistakes is to approach it to do the essential stuff and avoid the fluffy stuff.
bluGill 1 days ago [-]
Huh? The best way to prevent mistakes is to make doing it right easy. Manually doing everything just means a lot of room to mess up. Using the newer constructs my means you can't make a mistake as the hard work is done correctly for you. Zero cost abstraction means that it has no downside to manually doing it.
C++ is trivial compared to the code I work on. If you are writing hello world complexity then C++ might be complex but some of us work on hard problems.
jstimpfle 1 days ago [-]
I understand C++ as well, or better, than most (or all) of my peers, and certainly betters than people on here thinking they need to explain to me how RAII works. Do you want to argue that C++ RAII / objects stuff isn't complex and doesn't put considerably restrictions on how you design your app, then maybe you should reconsider.
I would argue that if cleaning up resources properly is among the hard problems, or among the most error-prone problems in your code, then maybe you're problems aren't that hard or complex after all.
I'm currently working on a distributed caching system and on real time voxel geometry boolean operations simulation (on GPU), both on the scale of >= 10^9. Is that "complex" enough? Both are done in C++. C++ helps exactly 0 in achieving any of these things (as opposed to using plain C), well the one help is I don't have to type 'struct' all the time.
In fact, in one of these projects I was pushed to use STL initially. I'm now working on getting rid of the last of them because we have had concurrency bugs and performance problems from using them. The code was not obvious and using STL containers (std::deque is very bad specifically) meant the actual runtime characteristics depend on which STL implementation is being compiled in. It would have been easier to just do straightforward obvious manual code.
feelamee 24 hours ago [-]
I'm interesting which C++ features also helps you to design such systems.
I think basic RAII/function overloading/templates should give a lot of capabilities comparing with plain C?
> (as opposed to using plain C)
Also, curious - how plain C helps with this?
jstimpfle 17 hours ago [-]
I've found that RAII and the stuff you have to buy into in order to use RAII come with more downsides than upsides once you scale beyond high level programs that try to get done a lot with very few lines.
> how plain C helps with this?
By staying out of the way and providing everything of what you actually need in the end. That is assuming a detail oriented approach where you deeply think about, and want to be flexible about, the organization of what your program should do. As programs grow into large architectures, and as programs get more performance conscious, they also get more detail oriented like that, and they tend to opt out of unflexible high level language features.
feelamee 13 hours ago [-]
thx, I don't understand fully, but see something truthy in this.
It would be interesting to read more detailed blog post about this.
seanhunter 2 days ago [-]
Yes. If anything, this is taking a complex yet common idiom and making it simpler.
usrnm 2 days ago [-]
Is it actually simpler, though? The unfortunate reality of this world is the fact that C++ is not the latest standard of the language or the newest shiny library, it's all of them at the same time. Adding a new way of doing the same thing decreases complexity only if you migrate all of the existing code, which nobody ever does.
hasley 1 days ago [-]
In cases where I assume that enough test coverage exists, I simplify code that I am currently working on or which I need to read very often.
This way I have already replaced a lot of for-loops by range-based for-loops. It helps me to understand code faster.
But code parts that noone needs to touch or see do not need to be more readable.
coffeeaddict1 2 days ago [-]
This is actually useful, but despite it is another extra thing you will have to remember when reading C++ code. I guess with LLMs things aren't so bad.
skrebbel 2 days ago [-]
Why? It’s still the good (bad)
old pimpl pattern. It just got a bit shorter. When reading you dont even need to grok “std::indirect”, you see the word pimpl and you know what’s going on.
coffeeaddict1 1 days ago [-]
Because I will still encounter people who use the old of writing pimpl. So I now I'm forced to remember the old way + new way. People aren't magically switching to use std::indirect. I bet that even in 10 years, half of projects will still be using the old way.
MaPi_ 2 days ago [-]
I don't really get why people keep repeating the "C++ is too big" complaint together with the implication that you need to remember the entirety of the standard library. In comparison Java has networking, GUI framework and even MIDI in its standard libraries. Is it because C++ is more closely related to C which library is so small that it barely contains anything useful? I much prefer code that uses a library feature rather than yet another poorly implemented and not documented hand rolled version of it.
dooglius 2 days ago [-]
Networking, GUI frameworks, and MIDI are presumably all self-contained and you would not need to be familiar with them except when working on networking, GUIs, or MIDI files, respectively. This is a general-purpose thing that could show up in any c++ code.
einpoklum 2 days ago [-]
You need to remember _less_, rather than more, when you use this kind of vocabulary types. Think about std::optional. Before that (and if you didn't write something like it yourself), you had to, for each class, remember the bespoke semantics of when and how it represents the lack of some members, and you would have to have non-defaulted ctors, move assignments and dtors, and then whenever you used that class you would need to think about what those custom method do, which might be different than other classes which have optional members. Now you just tell yourself "oh, it just has an optional member, no biggie". Look at my comment above regarding how short the implementation of Widget becomes when you squeeze the juice from having the rule of 0.
coffeeaddict1 1 days ago [-]
No. This is not what I meant. I absolutely agree that std::indirect is an improvement. My point is that when dealing with C++ code, now there is yet another way of doing the same thing. I will have to remember both ways, because people will still keep using the old way.
einpoklum 1 days ago [-]
Ok, well, you have a point, in that you might see the old way of doing things and you might see the new way. This is, however, one of the detriments of the language's commitment to backwards-compatibility: Old-C++ code is (almost without fail) valid new-C++ code.
When you write new code, this is (mostly) not an issue; when you have to maintain old code, it is. Especially if the existing codebase is somewhat of a patchwork of code introduced at different points in time - pre-C++98, C++98, C++03, C++11 and so on. For this reason it is a saintly virtue to manage to unify the C++ "vernacular" used in a project, for better readability by newcomers and for facilitating uniform changes to the entire codebase later on.
AnaSpelunker 2 days ago [-]
I thought C++ is unnecessarily complex, and then I see Rust following the same pattern... I've just thought of a complexity metric that would calculate the ratio of alphanumeric characters to punctuation.
And before it grows too big, it wastes memory. For your use cases it may not matter, and the saved pointer indirection may be more important, but maybe the person who has a million item vector of objects doesn't appreciate a 300% "just in case" memory overhead. The overhead may also hurt cache hits.
If you're doing this to save the pointer indirection, you should benchmark it for every use case, since negative cache effects may dwarf that gain.
Then again, extra padding can also help performance, for some workloads (especially multi threaded read/write against a vector of objects).
So without further context, there's no way to say if your way hurts or helps. It's certainly not a general solution.
how it wastes the memory? It's just a bytes array of the same size as struct. I would be more imposed on how idiomatic pimpl with heap allocation influences memory usage and cache hits
So you're only trying to solve compile time issues (incremental and not)? Maybe the right long term solution is C++ modules, instead? And maybe "just" a matter of having your build environment support modules?
[1] https://news.ycombinator.com/item?id=49034842
So regardless of what WG14 and WG21 do, compiler vendors will ignore them, if it means angry customers.
In design by committee languages, new standards are only relevant to the extent implementers actually care about them.
GNURadio consistently uses pimpl for blocks, as I understand it mainly for ABI.
> willing to stop improving.
I think that dismissing it like that shows a naive understanding of execution environments, binary interface design, and in general systems software engineering.
If you want the latest and greatest, a willingness to rebuild your code seems like a reasonable prerequisite to me.
If you need a binary blob that can withstand toolchain versioning, use a dynamically linked library.
The pimpl idiom is a C idiom where a header declares an opaque structure and prototypes of functions that take pointers to that structure. In C the OP example would look something like
In particular, in C `Widget` directly has `clicks` and `name` as fields.But in c++ we like to use methods on objects, and in order to do this, you need the class declaration in scope, which means your current compilation unit needs to have seen all of Widget's data members. In practice this means if you try to use "pimpl" in C++, you do something like the OP where there is a pointer to an opaque type inside your class.
However, this is not the same thing. Methods are called with a `this` pointer, which means every access to the internal structure adds a second pointer dereference. This is why this isn't the true pimpl -- it wastes an extra deref on every access.
You can get true pimpl in current C++ but it's a lot of boilerplate and heavily relies on compiler inlining. An implementation of the example from the OP: https://godbolt.org/z/6EznxeG1n . In practice this is too much work, hard to read, and so nobody does it.
For the c++ standards committee: please add an "opaque class" feature where the class can only define non-virtual method prototypes. Then the full class declaration, in the associated cpp file, could include its parent classes, actual data layout, and function implementations.
I am wondering why C++ can't implement "non-null" unique_ptr version in the same way? As I know, that the main argument against implementing it is, that it's can't be done, since move-out unique_ptr still can be null.
https://github.com/microsoft/GSL/blob/main/docs/headers.md#u...
What do you mean by move-out unique_ptr? That the not_null ptr type would ne null after it's been moved?
In that case that's just a plain usage error, same as how you could memset it to null.
I would love to proved wrong, but everything I can think of still leaves a footgun that's easy to trigger by accident, and thus negates the point of the solution.
I think the can't-reference-after-moved-from and objects-are-not-Copy-by-default are key to creating these types (at least enforced at compile time). And that would require major language changes, at least as big as the C++11 changes.
While many of us that like C++, would wish for a different evolution process, some of this stuff can be enforced by static analysis tooling.
Just like despite being safer than C++, we still use Sonar, FindBugs, FxCop/Roslyn Analysers, go vet, rust clippy, one more reason to actually use Sonar, clang-tidy, PVS, MSVC analyse,... with languages like C and C++.
In some of these you can add your own rules even, even if not always that straightforward.
I also like to use it sometimes to "hide" private methods and their documentation into PIMPL, so the public header is kept clean.
Yep, that's what I've used it for. Didn't find it too difficult to implement it myself, but I guess every bit of convenience/bug avoidance helps.
Not sure how much pimpl is used in reality, but it's a pretty ok solution to speed up build times (apart from unity builds), because it avoids having to include headers that are only needed for the private state into the public interface header.
To make hobby-coding fun, i use a mstdp.hpp that implements "naive" versions of unique,shared,function,etc that compiles faster than including just one of the std versions (and yes, MSVC versions of those libraries seem to be excessivly complex).
The import std is much faster than plain #include<iostream>.
Maybe it's time to move my hobby project over and see how well it works.
Best experience is VC++ with MSBuild, cmake/ninja work great with latest clang however import std support is not yet enabled by default in CMake.
I only care about VC++ for hobby coding, hence using modules.
See for example, https://github.com/pjmlp/RaytracingWeekend-CPP
But, no ICEs and it's working! I'll start writing some code with them tomorrow.
> Still best only used in implementation files, not headers.
We only compile implementation files!
With many of the features coming into the language over time, I kinda wish that a bit more restricted subset of it eventually becomes a thing, but I know in practice it might as well be a completely different language. That, and I expect that still many other things have not been resolved as well as they are elsewhere, such as build system and dependency management (although I haven't touched this stack for a while now, so I would love to be surprised).
You'd essentially like to derive a class/module implementation from it's corresponding class/module interface (which is all the user sees), but have the language automatically add a hidden "pimpl" pointer to the interface class. The implementation would then essentially use "this" to access public members, and "pimpl" for private members.
I was pondering on why he was putting the defaulted methods in the cpp, any particular reasons?
I did realize that the indirect version is required to be in the cpp since the header won't know how to copy without knowing the definition of the impl class.
1. click() should not be a member of the widget. A widget does not click; a user clicks a widget. A click can change a widget's state, but the state might change because of other effects, e.g. pressing a key when the widget is focused. But then, that's just one of the issues with treating UI widgets this way.
2. More to the point - clickCount. If this is a button, it shouldn't keep a record, or aggregate, of its clicks within it; and if it's a widget where this does really matter, like a range control where more clicks mean a value that goes farther along the range - you still would not keep the count of clicks, but the current position. Statistics about the interaction with an object should not be part of the object itself. At most it might be legitimate to have, say, a Widget class, a template like <class Stats> StatisticsTracker , and then class TrackedWidget which uses that as a mixin, i.e. inheriting both Widget and StatisticsTracker<ClickStats>. And that's already stretching it beyond what I would find reasonable.
3. Having something named is another aspect of objects which may be a good fit for a mixin class.
Anyway, an 'indirect' type for objects you don't know the definition of sounds nice.
A few more nitpickis about the example:
1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
and that's the beauty of the rule of 0.2. Why return an std::string for the label? The label() method should return an std::string_view
> 1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
The blog post explicitly explains why this doesn't work. You have to define these methods in the source file because they need to see the definition of the Impl struct.
This only works if it's always the same value. This doesn't work if the label is for example, set to `std::to_string(clickCount())`
All this complexity follows unique_ptr and copy constructor madness.
Anything with pointers with ownership should never be copied - period. Reference pointers - OK if scope/lifetime is known.
Can we have c++11 lite?
This "std::indirect" tries to have value semantics, but in fact it's just another type of smart pointer and uses pointer syntax (pimpl->foo : forced since C++ allows "->" as a user defined operator name, but not ".").
But it's a weird sort of "pointer" given this copying behavior, which is maybe why they didn't give it a "_ptr" name.
People will contend themselves with "C++ the good parts", helped by clang-tidy, PVS, MSVC analyse, and move on.
Because they spend so much of their time struggling with the pain points of the older code.
> but can't intuitively understand what it's doing
For (most?) new vocabulary types, it is rather intuitive to understand what they do. optional, variant, indirect - you may not remember the details by heart immediately, but you get the general idea and expect that they would behave in some reasonable way. And mostly, they do. That's not to say they're perfect: I feel like vomiting looking at std::variant's and how you have to work with them, as opposed to a proper case classes / algebraic union types in the language itself. And yet - when someone puts one in their class, instead of a bunch of code in a bunch of methods, you know what's going on. It does "read like a different language" somewhat, and that's good. The nicer language has been struggling to get out, as the saying goes.
Even classes are an instance of this, they were to solve some perceived problems, but they created much bigger issues, such as readability issues and introducing many more compile time dependencies.
PIMPL wasn't even a C++ feature but an idiom pushed by some people. It is next to unusable because you have to duplicate the API and write all the call forwards.
One problem with std::unique_ptr for example is that there is no ergonomic way to use it to hide implementations. The reason is it relies on destructors and to use destructors the class definition needs to be visible.
For me your comment seems pointless, because std::indirect have a precise and clear problem which it solves. And this is totaly not a "hackery fix of language drawback". In this case language works as intended and std::indirect just close the gap for facility which is still would be written by hands, if there is no std::indirect.
This here should not need to happen:
> document that moved-from objects cannot be used
It's moved from, so of course it cannot be used. Shouldn't have to add an assertion in every method to guard a fundamental invariant.
> In large codebases you will now have to deal with all three solutions being used, depending on how old the code is.
this is really a problem of "large codebases" and not C++ complexity. If people writing badly organized code.. than.. nothing will help them. Even such string language as Rust.
---
But, I go aside. I can be wrong here, but this is how I see this: - author author worked with C++ early in his career - now his work with something like python or javascript or "promptscript" or don't programming at all - he managed to taste C++ drawbacks - now he reading such article just to see "what's new" in technology he was interested in the past. - he see something what he don't understand from first sight - this looks like a complex and quirky thing => he decide that problem in the language, not in his incompetence in this area
I see such comments very often and usually people don't provide any technical expertise. But just their feeling that "oh, this thing was so complex, now it even more complex".
Nobody needs "deep copying", ever. It's not even well defined what it should mean (i.e. how deep etc.). It's purely a theoretical problem with no good practical (one-fits-all) solution. The only practical way is to copy what you need copied, when you need it. Done.
C++ is trivial compared to the code I work on. If you are writing hello world complexity then C++ might be complex but some of us work on hard problems.
I would argue that if cleaning up resources properly is among the hard problems, or among the most error-prone problems in your code, then maybe you're problems aren't that hard or complex after all.
I'm currently working on a distributed caching system and on real time voxel geometry boolean operations simulation (on GPU), both on the scale of >= 10^9. Is that "complex" enough? Both are done in C++. C++ helps exactly 0 in achieving any of these things (as opposed to using plain C), well the one help is I don't have to type 'struct' all the time.
In fact, in one of these projects I was pushed to use STL initially. I'm now working on getting rid of the last of them because we have had concurrency bugs and performance problems from using them. The code was not obvious and using STL containers (std::deque is very bad specifically) meant the actual runtime characteristics depend on which STL implementation is being compiled in. It would have been easier to just do straightforward obvious manual code.
> (as opposed to using plain C)
Also, curious - how plain C helps with this?
> how plain C helps with this?
By staying out of the way and providing everything of what you actually need in the end. That is assuming a detail oriented approach where you deeply think about, and want to be flexible about, the organization of what your program should do. As programs grow into large architectures, and as programs get more performance conscious, they also get more detail oriented like that, and they tend to opt out of unflexible high level language features.
This way I have already replaced a lot of for-loops by range-based for-loops. It helps me to understand code faster.
But code parts that noone needs to touch or see do not need to be more readable.
When you write new code, this is (mostly) not an issue; when you have to maintain old code, it is. Especially if the existing codebase is somewhat of a patchwork of code introduced at different points in time - pre-C++98, C++98, C++03, C++11 and so on. For this reason it is a saintly virtue to manage to unify the C++ "vernacular" used in a project, for better readability by newcomers and for facilitating uniform changes to the entire codebase later on.