Logo

Blog


Using C++23s constexpr unique_ptr

Back in 2022, my paper P2273R3: Making std::unique_ptr constexpr was accepted for C++23. All the time, I planned to provide an implementation for libc++ but never found the time. Recently, I discovered that somebody else was so kind and implemented my paper.

Time to show you when a constexpr unique_ptr helps you.

An example

This is an example from my C++20 book Programming with C++20 - Concepts, Coroutines, Ranges, and more.

What I want to illustrate is a very rough sketch of a car racing game. I have a couple of different car brands and a factory function capable of creating a car, returning a base pointer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Car {  A Base class for all cars
  virtual ~Car()                      = default;
  constexpr virtual int speed() const = 0;
};

B Various concrete cars with individual speed
struct Mercedes : Car {
  constexpr int speed() const override { return 5; }
};
struct Toyota : Car {
  constexpr int speed() const override { return 6; }
};
struct Tesla : Car {
  constexpr int speed() const override { return 9; }
};

C A factory function to create a car
constexpr Car* CreateCar(int i)
{
  switch(i) {
    case 0: return new Mercedes{};
    case 1: return new Toyota{};
    case 2: return new Tesla{};
  }

  return nullptr;
}

Later somewhere in the game, I want to determine the fastest car available. Assume that a car could be broken or require maintenance, some reason for it to be less fast than initially or not present at all. Therefore I have the function FastestCar, which walks over the set of cars and returns the index of the fastest available car at the moment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
constexpr int FastestCar()
{
  int max   = -1;
  int maxId = -1;
  for(int i = 0; i < 3; ++i) {
    const auto* car = CreateCar(i);

    if(car->speed() > max) {
      max   = car->speed();
      maxId = i;
    }

    delete car;
  }

  return maxId;
}

While, of course, most use will be during run-time, there are two use cases for FastestCar at compile-time.

Doing something at compile-time

Suppose you want to check, at compile-time, that the fastest car is a specific one. Or, alternatively, you want to determine the fastest car such that you can render this image at compile-time as part of the boot screen of the game. Let's use the first scenario.

1
2
3
4
5
6
void Use()
{
  auto* c = CreateCar(1);

  static_assert(FastestCar() == 2);
}

Above I use FastestCar in a static_assert to verify that Tesla is the fastest available car. While this entire code works, the implementation of FastestCar is the issue.

Oh no, raw pointers

If you look closer at it, you can see that I use a raw pointer car that holds the result of CreateCar. Due to that raw pointer, I also require a matching delete. But that's pre-C++11-like code. I teach stop using raw pointers, and yet I have to show examples and write code that uses raw pointers.

C++23 to rescue

Luckily in C++23, I have a better solution thanks to P2273. I can now switch to a unique_ptr and eliminate the raw pointers in CreateCar in favor of std::make_unique at the same time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
constexpr std::unique_ptr<Car> CreateCar(int i)
{
  switch(i) {
    case 0: return std::make_unique<Mercedes>();
    case 1: return std::make_unique<Toyota>();
    case 2: return std::make_unique<Tesla>();
  }

  return nullptr;
}

constexpr int FastestCar()
{
  int max   = -1;
  int maxId = -1;
  for(int i = 0; i < 3; ++i) {
    if(auto car = CreateCar(i); car->speed() > max) {
      max   = car->speed();
      maxId = i;
    }
  }

  return maxId;
}

My code now looks like normal code but is constexpr-able. I hope P2273 simplifies your life and code as well!

If you want to know why I'm a fan of constexpr functions, you might find my post constexpr functions: optimization vs guarantee helpful.

Andreas