Not a C++ developer but nodiscard is gross in my opinion. I've seen codebases littered with it for no good reason. Why should you care if the caller uses the return value or not?
Honestly it's kind of useful as a customized warning to callers. Callers can still ignore it by, for example (void)funcall_with_nodiscard(args). But they have to explicitly declare the intent to ignore the result. That seems like an all-around fair construct.
> Why should you care if the caller uses the return value or not?
Nodiscard is a way of “enforcing” a contract with the user about how your code needs to be used in order to avoid undefined behaviour.
(I say “enforcing” in quotes because, instead of being an actual constraint, it’s merely an attribute - so a conforming compiler can happily ignore it)
Here are two examples of where it is useful:
- Returning a success code that must be checked before proceeding with an operation that could have bad consequences if the previous operation failed. Unchecked operations are one of the major sources of bugs in the wild, so no discard at least points the user to the potential problem.
- Returning something that doesn’t make any sense to immediately discard. This is usually down to a mistake - such as calling vector::empty thinking that it’s going to clear the vector, when it actually returns a bool telling you if it’s empty or not (an awful name, but then so is vector…). It makes no sense to check if it’s empty without using that result, so the warning indicates that the user has made a mistake.
Because a function can be assumed to work one or two ways and nodiscard can prevent you from assuming the wrong thing. Imagine a fake string::concat(other) that does what it says on the tin. You can assume it appends to s but what if it allocates a new str instead? You label it with nodiscard because there is literally zero purpose in ever calling this method but ignoring the result - if you ignore it, it’s as if it was never called (since the existing variables were not modified) except you paid the price with the allocations. So either use the result or drop the call.
Using it to enforce that every return value is handled is stupid as it can lead to error blindness and you’ll ignore it when you actually need it.
So that they don't misuse it. A good example is vec.empty(). There's no point calling vec.empty() without checking its return value. It helps distinguish noun vs verb.
Some functions are pointless without their return value, not using the return value is most likely a bug, and it is good to have a warning. If using the function without using the return value is not a bug, then there is a problem with your codebase.
std::clamp ; I got it exactly wrong and would have introduced a bug if the compiled didn't tell me I was discarding the return value. I thought it updated the value I wanted clamped. It does not. Don't know why I thought that, but I did.
The function is entirely useless and pointless to call without using the returned value, and [[nodiscard]] pointed this out to me. Perfect.
Everyone is mentioning error codes but I think a better use is when the function returns some allocated memory, where ignoring the return value would be a bug. I’m not convinced nodiscard is very useful, however.
If you're returning raw pointers in c++20 something has gone off the rails already. If the memory is wrapped in some kind of RAII structure, then why is it any more of a bug to ignore it than any other return value?
I guess. My current project is spacecraft flight software targeting C++98 on GCC 4.3ish. Still we backported a slightly gimped `shared_ptr` and a much more gimped `unique_ptr` for the incredibly limited amount of dynamic allocation we do on startup.
I know there are a lot of people out there still writing C and I've come to accept that I will never understand or agree with those people, but to be writing C++ and passing raw pointers around in 2023 is a hell of a choice IMO.