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

  • @corroded@lemmy.world
    link
    fedilink
    2
    edit-2
    6 days ago

    When would this actually be necessary? Realistically, I see two possibilities.

    1 - The function’s purpose is to generate a result value, meaning that ignoring the result is a waste of a function call and any resources consumed by the function. In this case, though, nobody would ever be calling the function unless they specifically wanted the result.

    2 - The caller must take ownership of the result. In this case, just don’t return raw pointers.

    The addition of [[nodiscard]] suggests that there are valid reasons, but I have never personally come across one.

    • @lambaliciousOP
      link
      English
      16 days ago

      Consider, say, the printf family of functions. The side-effect of their invocation is quite notorious and clear: print something to the screen, or write it to a file, etc. This kind of thing is not expressable (and should not be expressed as) as a return type. But these functions do have a return value: a status code indicating whether the write-to-medium was successful or else why. It’s so easy to discard this information and end up eg.: ignorin a write that didn’t happen because there was not enough room, or writing more bytes than the destination buffer could afford to take (hellooooo, buffer overflow!).

      There are lots of functions (probably entire categories, but I’m not that strong on type theory) where the “result” is vastly different from the “return” or can not be expressed as such. Foremost cases I can come up with are I/O, stuff on complex types where types also represent actions or components with side effects (eg.: GUIs), and pretty much anything about inserting or removing elements from containers. In those cases, if the two things differ but the return is also important, it’d be nice to have a mechanism to make sure that it can’t be accidentally ignored (explicit, intentional ignoring is fine; that’s what we have (void)(expr...) in the language).

      Another reason for having this capability is having a function with a composite return-result type, such as a std::expected<T,E> where you want to help make sure the composition is handled correctly by the caller.

    • @lambaliciousOP
      link
      English
      1
      edit-2
      14 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.