To be really pedantic, there is one thing a reference can do that a pointer cannot: prolong the lifetime of a temporary object.
When you bind a const reference to a temporary object in C++, the object's lifetime becomes the reference's lifetime.
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
In this case, s3 copy duplicates the temporary object created by concatenation.
In contrast, s3 reference effectively becomes the temporary object.
It's actually a pointer to a temporary object, which now has the same lifespan as the pointer.
If you try to compile this without the const, it should fail.
You can't bind a non-const reference to a temporary object, and you can't even get its address.