In the previous blog post, I shared some thoughts on [[nodiscard]]. As I wrote there, it would be better to have a good default of always assuming [[nodiscard]], and opt out from that good default only in some specific cases. But, unfortunately, the current state of the C++ language standard is different, and actually the opposite of the above.
So, where would I strongly recommend to apply [[nodiscard]]?
The Case of Bad Confusing Naming Choices
A common case that comes to my mind is the example of std::vector::empty. I have always found that vector’s method name confusing. I mean: Is “empty” a method to empty the vector, that is to remove its content? No. std::vector::empty is a bool-returning method to check if the vector has no element.
So, in this case, applying [[nodiscard]] to the vector::empty method will make the C++ compiler generate a warning when you write this kind of C++ code:
// myVector is a std::vector
// Empty the vector content [NOTE: Wrong code]
myVector.empty();
As can be read from the comment above, the intention of the programmer was to empty the vector. But the vector::empty method simply returned a bool to check if the vector had no elements. And, in the above case, that returned value was discarded.
So, the use of the [[nodiscard]] attribute with the vector::empty method will generate a warning in the above bogus case. It’s like the C++ compiler telling the programmer: “Hey, programmer! You discarded the return value of vector::empty!” So the programmer thinks: “Hey, what return value? Wait a minute… I just wanted to empty the vector! Ahhh…Ok. I got it: This vector::empty method returns a bool and is used to check if the vector is empty. So, the method I should call is another one!” And then, after some search, the vector::clear method is invoked and the above code fixed.
// Correct code to empty the vector:
myVector.clear();
Note: The so maltreated MFC, with its CArray class, did choose a much better naming for its “empty” method, calling it IsEmpty. With such a good name, you won’t certainly make the mistake of invoking IsEmpty when you actually meant to make the vector empty. The designers of the STL made a confusing choice for the vector::empty method: calling it is_empty would have been much better and clear.
So, this is actually a case in which [[nodiscard]] comes to the rescue for a bad confusing choice for naming methods.
The Case of Returning Raw Owning Resource Handles
Another important case where I strongly suggest to apply the [[nodiscard]] attribute is when you return to the caller some raw owning handle or pointer (which you can think of as an “handle” to memory resources).
For example, in my WinReg C++ library, I wrap a raw Windows Registry HKEY handle in the safe boundaries of the RegKey C++ class.
However, for flexibility of design, I chose to have a RegKey::Detach method that transfers the ownership of that HKEY raw handle to the caller:
// Transfer ownership of current HKEY to the caller.
// Note that the caller is responsible for closing the key handle!
[[nodiscard]] HKEY Detach() noexcept;
After invoking that method, the caller becomes the new owner of the returned HKEY. As such, the caller will be responsible for properly tracking and releasing the raw handle when not needed anymore.
In such cases, discarding (by mistake) the returned value of the RegKey::Detach method would cause a resource leak. So, having the C++ compiler speak up in such cases would definitely help in preventing such leaks. So, if you have to be selective of where you apply the [[nodiscard]] attribute, this is certainly a great place for it!