Why use pointers at all? Just use const references for immutable stuff, regular references when you want mutation, normal values when you want copy semantics, and smart pointers for other cases (unique_ptr for move semantics, shared_ptr for lack of clear ownership, etc.) If you want to represent nullability, std::optional is a good choice.
> If null is not a valid value, throw an assert at the beginning of the function
Yikes. This means the compiler will not catch it for you. Why make this a runtime issue? Use the type system to your advantage.