Yes but pimpl is really just a hack and workaround for the fact that you can't separate the public interface from the implementation details in C++ due to needing to put everything in the class definition. imho `partial` is superior, as you don't need an additional allocation and indirection.
You actually don't need to do that at all. It's common style in C++, but the language does not require it.
With the right techniques, you can absolutely forward-declare basically all of a class's functionality. Then you can put it into its own translation unit.
Members and function signatures have to be declared in the header, but details about member values/initialization and function implementations can absolutely be placed in a single translation unit.
The data layout of a class must be part of its definition. So either you expose the layout (data members), add a layer of indirection with PIMPL, or resort to ugly hacks to otherwise hide the data layout such as having buffers as members. Another possibility is to not use exposed classes for member functions. Then you can just pass pointers around only and never use C++ features. Out of all of these, PIMPL solves the problem the best.
Yes, that's true. But if the concern is build-times, exposing the data layout is harmless. We don't necessarily need full PIMPL just to get improved build times. By keeping the data layout in the .hpp, you can guarantee that your class can still stack-allocate.
if the concern is build-times, exposing the data layout is harmless
Not at all! If your private member variables use any interesting types (and why shouldn't they?) you need to include all the headers for those types too. That's exactly why you get an explosion of header inclusion.
If you change the data layout of your exposed class, you must recompile anything that uses it. That increases build times and also breaks ABI. And as the other guy commented, the data itself has types that also need definitions and file inclusions. Without PIMPL, your data layout can change without touching your header, due to changes in a related header (even a 3rd party header).
You don't need any indirection with partial because in C# all classes already go through a layer of indirection.
However, consider that in C# if you add a field to a struct, which is a value type and hence no indirection, then you do need to recompile all assemblies that make use of that struct. It's no different than C++ in this regard.