My boys! We love testing, don't we? More #cpp / #cxx questions for you to sharpen your wits and train your legalese on. Because when it comes to regulations, it's important to not leave anything to chance, even more now that the military says they want to loooooove C++ (in a "why can't you be like Rust?" way).
So:
How to make sure the caller of your function does not ignore the result?
[ ] Mark the function as "[[nodiscard]]", as attributes are totally mandatory
[ ] Return the result as an exception, via "throw result;"
[ ] Add a // comment explaining to not ignore, and let the client's IDE do the rest
[ ] Return the result via an out-parameter, so the user has no option but to be aware of it
Here’s the thing: it’s not [[nodiscard], at least how it’s currently implemented.
[[attribute]]s are by design ignorable: placing them or not should not change the validity of the program; so if I call [[nodiscard]]function(...) the effect should be the same as calling function (and eg.: not using the result).
Nodiscard, and any such attributes, can at most emit a warning, but a warning is not an error. Moreover, attributes that a compiler does not understand or has disabled are, by design, safe to ignore, which means a, say, C++11 version compiler can safely and without warnings not-warn about a function marked [nodiscard]] (which is C++17). Essentially, [[nodiscard]] is a recommendation, not an obligation.
intdiscardable (int arg1, int arg2);
int also_discardable [[nodiscard]] (int arg1, int arg2);
intmain () {
discardable(1, 2); // must compile
also_discardable(1, 2); // *must* also compile!
}
throwing the result guarantees that the resulted value van not be ignored… at runtime, when an exception will be generated. During compile your code still compiles, even without a code block to catch that exception, without any notice; and it also likely leads to changing the called function’s signature. Add to that that exceptions are leaky, costly, non-deterministic and even forbidden at some places, and it’s not really a viable option if you care about any of size, speed or falsiability.
Adding a // comment is… well… a comment. Not even part of the code proper. Once again: recommendation, not obligation.
Using an out-parameter to return the result forces the caller to at least give the out-storage for it some sort of name: potentially create a variable of reference, pointer or wrapper type, give it a name and pass it as an argument:
voidmyfunction (int arg1, int arg2, int* result);
intmain () {
// you have to create a variable to get the resultint result;
myfunction(1, 2, &result);
// but note, still, you can cheat your way out of it
myfunction(1, 2, newint);
}
The signature of the function has to be changed to account for it, so you can’t really miss that there’s an extra parameter… but you can mmiss what the parameter is for: nothing in the current C++ standard toolkit actually functions like an out-parameter, you don’t know from looking at the signature of a function if the reference or pointer you are passing are used to read data or to store data, you’d have to go read the manual or the code. And documentation is recommendation, not obligation.
Still, at least from my perspective, the best option is to use an out-parameter, with using [[nodiscard]] a decent second option. Returning via throwmight be a better option in a world with deterministic, value-based exceptions.
I don’t have a CS education, why would it be anything other than [[nodiscard]]?
Here’s the thing: it’s not
[[nodiscard]
, at least how it’s currently implemented.[[attribute]]
s are by design ignorable: placing them or not should not change the validity of the program; so if I call[[nodiscard]] function(...)
the effect should be the same as callingfunction
(and eg.: not using the result).Nodiscard, and any such attributes, can at most emit a warning, but a warning is not an error. Moreover, attributes that a compiler does not understand or has disabled are, by design, safe to ignore, which means a, say, C++11 version compiler can safely and without warnings not-warn about a function marked
[nodiscard]]
(which is C++17). Essentially,[[nodiscard]]
is a recommendation, not an obligation.int discardable (int arg1, int arg2); int also_discardable [[nodiscard]] (int arg1, int arg2); int main () { discardable(1, 2); // must compile also_discardable(1, 2); // *must* also compile! }
throw
ing the result guarantees that the resulted value van not be ignored… at runtime, when an exception will be generated. During compile your code still compiles, even without a code block to catch that exception, without any notice; and it also likely leads to changing the called function’s signature. Add to that that exceptions are leaky, costly, non-deterministic and even forbidden at some places, and it’s not really a viable option if you care about any of size, speed or falsiability.Adding a
// comment
is… well… a comment. Not even part of the code proper. Once again: recommendation, not obligation.Using an out-parameter to return the result forces the caller to at least give the out-storage for it some sort of name: potentially create a variable of reference, pointer or wrapper type, give it a name and pass it as an argument:
void myfunction (int arg1, int arg2, int* result); int main () { // you have to create a variable to get the result int result; myfunction(1, 2, &result); // but note, still, you can cheat your way out of it myfunction(1, 2, new int); }
The signature of the function has to be changed to account for it, so you can’t really miss that there’s an extra parameter… but you can mmiss what the parameter is for: nothing in the current C++ standard toolkit actually functions like an out-parameter, you don’t know from looking at the signature of a function if the reference or pointer you are passing are used to read data or to store data, you’d have to go read the manual or the code. And documentation is recommendation, not obligation.
Still, at least from my perspective, the best option is to use an out-parameter, with using
[[nodiscard]]
a decent second option. Returning viathrow
might be a better option in a world with deterministic, value-based exceptions.