Hello @spedler ,
And if you do that, trying to change any parameter using the (now const) BaseContainer also results in errors. So to get this to work you need to do this:
const BaseObject* op = static_cast<const BaseObject*>(node);
BaseContainer* data = const_cast<BaseContainer*>(op->GetDataInstance());
You should never do this, const_cast
things passed to you, as you can potentially crash Cinema 4D in doing so. Cinema 4D assumes op
to be const in this case, and you removing the constness of the data container violates this assumption. Doing this can be okay, when you defined for example op like this:
const BaseObject* const op = doc->SearchObject("Foo"_s);
Here casting away the constness of op
(or its data container) is okay, because it is only yourself who relies on that. I would avoid doing this even here though, as you might confuse yourself in the long run with doing such things. But when Cinema 4D passes you somewhere something const
, e.g., a const BaseObject* op
, then you must stick to that more than ever, because Cinema 4D is more parallelized in the backend than ever. The major point of the 2024 release is that mutability of data is a binding contract you should stick to (to allow for parallelism).
When your op
is immutable, this also means that you cannot change its data container.
const BaseObject* const op;
// We can get the immutable container, the version of GetDataInstance which returns
// an immutable container is a const method, i.e., can be called on a const instance.
//
// // The const behind the parenthese marks this method (not its return value) as const.
// // It is the contract that calling this method will not change the instance it is called
// // upon, #this remains const.
// const BaseContainer* GetDataInstance () const
const BaseContainer* const bc = op->GetDataInstance();
When op
is not const, you can also call the non-const overload of GetDataInstance
:
BaseObject* const op;
// We can still call this to get the immutable data container ...
const BaseContainer* const bc = op->GetDataInstance();
// ... but also this to get the mutable data container.
BaseContainer* const other = op->GetDataInstance();
No wonder the SDK examples just stick with C-style casts!
Hm, we changed a crazy amount with something like 40,000 casts in our code base with 2024. This already included some parts in the SDK, but the work is far from done. Removing all the C-style casts in our code base will be quite a bit of work (it also wasn't me who had this unthankful task, so I am only speaking second hand here).
In general, I would point to our updated style guide regarding casting, I outlined there the new rules, as C-style casting is only prohibited in cases where it is dangerous. Casting atomic types in this manner is fine (but also not recomended).
Where you should be careful is casting away const
-ness as you did above with:
BaseContainer* data = const_cast<BaseContainer*>(op->GetDataInstance());
or as you can do unintentionally (which is one of the reasons why we want to avoid them) with C-style casts:
const BaseList2D* const node;
// Not only up-casts #node to a BaseObject, but also implicitly removes its constness.
BaseObject* const op = (BaseObject*)node;
Cheers,
Ferdinand