(Only half joking with the poll options, too.)

  • lambalicious@lemmy.sdf.orgOP
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    4 days ago

    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.

    
    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!
    }
    
    

    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:

    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 via throw might be a better option in a world with deterministic, value-based exceptions.