Drew Dormann's excellent answer above resolves the core confusion that I had. Adding a summary here (since it is too long to be posted as a comment) for complete clarity.
Doubt 1 : How are we able to use T&
as the return type even though std::unique_ptr::operator*()
is marked a const function?
Answer 1 : std::unique_ptr
just stores the pointer to the data being managed (the data itself is not stored within the class, but rather on the heap). We're able to mark its operator*()
function as const because we're returning a non-const reference to the actual data being stored & since that data itself is not stored inside the class, the 'const' qualifier just makes the pointer to the data const (i.e. we have a const pointer) but the data that this const pointer points to is still non-const and we can bind non-const references to it.
Doubt 2 : The same thing is true for std::vector
(i.e. the data managed by it is not stored within the class, but on the heap instead) but then why we don't similarly mark functions like std::vector::front()
, std::vector::back()
etc as const?
Answer 2 : This is due to the semantics associated with container classes in C++. Quoting Drew from above - std::vector models properties of an array, while a std::unique_ptr models properties of a pointer. So we kind of act as if the data managed by std::vector
resides inside the class. Note that std::optional
is different from std::unique_ptr
/ std::vector
as the data being managed is actually stored within the class.