Uniform access to runtime and compile time values

While writing some of the C++20 code related to NTTP I stumbled upon a rather minor but nonetheless interesting issue.

Illustrative example

In C++20 we have std::span with T and Extent template parameters, where Extent can be either some size_t value known at compile time or std::dynamic_extent which is basically (size_t)(-1) that represents some runtime value.

Suppose we have our own type-container with optionally provided compile-time size:

template <typename T, std::size_t Size>
class Container {
  ...
};

And some deduction guides for the type:

template <typename T, std::size_t Size>
Container(T (&)[Size]) -> Container<T, Size>;

template <typename T, std::size_t Size>
Container(std::vector<T>) -> Container<T, std::dynamic_extent>;

And a member function that returns the size of the container:

constexpr std::size_t size() {
  if constexpr (Size == std::dynamic_extent) {
    return size_; // some member value that is set in the constructor
  } else {
    return Size;
  }
}

This pattern of returning a value in case it is known at compile-time with if constexpr is pretty useful and simple but not very handy because if constexpr is not an expression that returns value and thus has to be wrapped into a function for each bit of the logic that requires reading the value. We can do better.

Wrapping the value

A very simplified example of a wrapper that could simplify the code above is something like this:

template <typename T, T Dynamic, T Value = Dynamic>
class CompileRuntimeValue {
  constexpr static inline bool kStatic = Value != Dynamic;
  constexpr static inline bool kDynamic = !kStatic;

 public:
  constexpr CompileRuntimeValue()
    requires(kStatic)
  = default;

  constexpr CompileRuntimeValue()
    requires(kDynamic)
  = delete;

  constexpr CompileRuntimeValue(T value)
    requires(kDynamic)
  : value_(std::move(value)) {}

  constexpr T operator*() {
    if constexpr {
      return value_;
    } else {
      return Value;
    }
  }

 private:
  struct Empty{};
  [[no_unique_address]] std::conditional_t<kDynamic, T, Empty> value_;
};

By wrapping the value into a type with a specific indicator that the value is dynamic allows to write the if constexpr once for all of the uses of compile/run time value.

Any CompileRuntimeValue object could also be marked with [[no_unique_address]] to hint the compiler that the value stored in the type itself. And applying that to the Container class:

template <typename T, std::size_t Size>
class Container {
  ...
  constexpr std::size_t size() {
    return *size_;
  }
  ...

 private:
  // initialized only in 'runtime' constructors, takes no space when Size is a compile-time value
  [[no_unique_address]] CompileRuntimeValue<std::size_t, std::dynamic_extent, Size> size_;
};