C++26 brings us many new tools, some big ones like contracts, reflection, structured concurrency, but even many smaller useful ones.

In this articles series (2 episodes) we will investigate on “DEEP” copies. This is now easily feasible due to the following 2 constructs:

  • std::indirect
  • std::polymorphic

The use case is:

  • we want to allocate something on the heap
  • we want to be able to copy that thing, in an easy way, without manually solving the problems this inherently has

In this article we will focus on std::indirect, the polymorphic types are for the next episode.

Our toy type is: (just imagine this is huge or for some other reason we want it on the heap)

Gcc16 already has support for this, clang not yet.

What does not work ?

Let’s look at some attempts that do not work.

Imagine we would store the allocated item as a raw pointer (assume we will perfectly delete at the correct time):

To bad, we didn’t copy the pointed to object, we just copied a pointer, and now have 2 pointers pointing to the same instance. Even more troubles got introduced.

If we want this to work, we need to manually solve it (new allocation, and copy construction/copy assignment).

Let’s improve, let’s allocate with make_unique, and as such it is safely stored in a unique_ptr. Now we can’t even copy, since we ended up with a move only type.

Again manual intervention to make it work is needed.

Ok, let’s do make_shared, and have it stored in a shared_ptr. This can be copied, but again we are not copying the object, we are just sharing it with one more party.

The solution: C++26: std::indirect

We have a Coordinate co1 which is created on the heap, but with value (like) properties. It is a pointer, so we need to dereference it.

So let’s make a copy, next adjust the original and inspect for both the x value. We can see, the original was modified and our copy remained unchanged. So for sure not 2 pointers pointing to the same instance.

Giving the output:

1
100
1

A next example, let’s have a struct with 2 indirect members:

And play with it:

this gives the following output:

1000 2000
100 200

Another way to address the previous example is:

This give the following output:

4000 5000
400 500

There is more: constness preserved

With raw pointers, or even smart pointers, we can make the pointer const, but this still allows us through that const pointer to modify the pointed to object.

The new C++26 constructs also fix this, a const std::indirect (and std::polymorphic) are CONST, uppercase const 😉 , also the dynamically allocated object is const.

Let’s try it out, and see the compiler complain:

We get the following error from gcc16:

 <source>: In function 'int main()':
<source>:92:12: error: assignment of member '{anonymous}::Coordinate::x' in read-only object
   92 |     co1->x = 100;  /// nope. const, read-only object
      |     ~~~~~~~^~~~~
Compiler returned: 1

Moving

Can std::indirect<T> be moved from ? YES

And it can be determined if the value had been moved from by calling the member method : valueless_after_move

Which gives the following output:

true
false

Summary

We have seen, we can now make deep copies of concrete types we allocated on the heap, by means of std::indirect, and we also get preservation of const.

Fyi, this allows classes using the PIMPL idiom to become copyable without effort.

In the next episode, we will look into std::polymorphic.

You can play with all the code snippets mentioned above here: https://godbolt.org/z/6Msh8dffz


Leave a Reply

Your email address will not be published. Required fields are marked *