15

C++17 has a new attribute, [[nodiscard]].

Suppose, that I have a Result struct, which has this attribute:

struct [[nodiscard]] Result { }; 

Now, if I call a function which returns Result, I got a warning if I don't check the returned Result:

Result someFunction(); int main() { someFunction(); // warning here, as I don't check someFunction's return value } 

This program generates:

warning: ignoring return value of function declared with 'nodiscard' attribute [-Wunused-result]

So far, so good. Now suppose, that I have a special function, for which I still want to return Result, but I don't want this warning generated, if the check is omitted:

Result someNonCriticalFunction(); int main() { someNonCriticalFunction(); // I don't want to generate a warning here } 

It is because, someNonCriticalFunction() does something non-critical (for example, something like printf - I bet that no-one checks printf's return value all the time); most cases, I don't care if it fails. But I still want it to return Result, as in some rare cases, I do need its Result.

Is it possible to do this somehow?


Possible solutions which I don't like:

  • I would not like calling it as (void)someNonCriticalFunction(), because this function is called a lot of times, it is awkward
  • creating a wrapper around someNonCriticalFunction(), which calls (void)someNonCriticalFunction(): I don't want to have a differently named function just because of this
  • removing [[nodiscard]] from Result, and add it to every function which returns Result
16
  • 9
    It seems like you have contradictory requirements. You want to add a warning so you never ignore a result, but you want to ignore it ? Commented Nov 3, 2017 at 21:03
  • 5
    If it's a special case then it should really not happen much, and using a cast to void is acceptable (with a comment telling why). And if it does happen often then it's no longer special and I recommend you take some time to think about your design and requirements. Commented Nov 3, 2017 at 21:04
  • 1
    Rust does this by returning a Result<()> (a result type that returns an empty struct). You then just call .unwrap() to ignore the error. Maybe you could add an ignore function to your Result class. Commented Nov 3, 2017 at 21:05
  • 5
    You've ruled out all reasonable solutions. Commented Nov 3, 2017 at 21:05
  • 1
    Also, it also increases maintianability and readability and discoverability to have [[nodiscard]] for the functions IMO. And it does make it much easier to handle "special cases". Commented Nov 3, 2017 at 21:18

5 Answers 5

38
+250

Why not make use of std::ignore from the <tuple> header—that would make the discard explicit:

[[nodiscard]] int MyFunction() { return 42; } int main() { std::ignore = MyFunction(); return 0; } 
Sign up to request clarification or add additional context in comments.

6 Comments

I really like this solution. It's quick to write, and obvious what it is doing. Plus, it's easy to search for when you want to fix the problem properly!
The OP said he didn't want to use (void)MyFunction() because it's called a lot and it's "awkward", but prefixing all calls with std::ignore = is even more extra syntax than (void), and adds a <tuple> dependency. I think he's just got to suck it up. The Result struct is being annoying and sometimes that's life.
@BillyDonahue Of course OP is going to have to suck it up. Given that, they might remember the need to write expressive and clearly intentional code. (void)MyFunction(); does not express intent and may be overlooked for its effect during a review. std::ignore = MyFunction(); doesn't suffer from those.
@sehe (void) is C-compatible, well-known and idiomatic, and more generic-friendly. It can be applied to expressions that might already be void expressions. std::ignore has a tuple-related purpose and the intent is more, not less, obscure when it is used for a purpose for which it wasn't designed.
So are raw pointers, error codes, and a host of idioms that have been made extinct. If you’re goig to argue that English words aren’t expressive, go right ahead, but I’m saying your reaching. Std::ignore has the benefit of expressing intent. It’s almost funny because “to ignore” is a word that also has a purpose. I’d say you can use it with the intent to convey its meaning.
|
8

I recommend the option you ruled out:

"removing [[nodiscard]] from Result, and add it to every function which returns Result."

But since you don't seem happy with it, here's another solution, using bog-standard inheritance:

struct [[nodiscard]] Result { }; struct DiscardableResult: public Result { }; 

For the functions where you can discard the result, use DiscardableResult as return type:

Result func1(); DiscardableResult func2(); func1(); // will warn func2(); // will not warn 

13 Comments

I've just asked this, because it seems that [[nodiscard]] is sticky, so DiscardableResult gets the [[nodiscard]] attribute (I've checked with gcc). But this could be a good solution, if [[nodiscard]] would not be inherited. stackoverflow.com/questions/47104799/…
Btw, why do you recommend adding [[nodiscard]] for every function? To me, [[nodiscard]] belongs to Result, as it always stores an error condition, it always must be checked. I have several hundreds of functions which return Result. Marking all of them, and maintaining them afterwards is tedious. Marking Result is so much simpler.
@geza: "Btw, why do you recommend adding [[nodiscard]] for every function?" If it is ever reasonable to discard the type, then the type should not be [[nodiscard]]. That's what it means to apply [[nodiscard]] to the type: that the very nature of the type itself means you should never discard it.
@geza: "as it always stores an error condition, it always must be checked" -- Your question contradicts that.
@geza: You're asking how to not have to check something that, supposedly, "must always be checked". That's a contradiction.
|
7

They say that every problem in computer science can be solved by adding another layer of indirection:

template <bool nodiscard=true> struct Result; template <> struct Result<false> { // the actual implementation }; template <> struct [[nodiscard]] Result<true> : Result<false> { using Result<false>::Result; }; 

This is effectively making Result conditionally [[nodiscard]], which allows:

Result<true> someFunction(); Result<false> someNonCriticalFunction(); int main() { someFunction(); // warning here someNonCriticalFunction(); // no warning here } 

Although really, this is identical to:

  • removing [[nodiscard]] from Result, and add it to every function which returns Result

which gets my vote to begin with.

2 Comments

Now he's got to go to all the functions where he returns Result, and change them to Result<true>. But if he's going to do that, he could just as well add [[nodiscard]] to the function (which is really what he should do, except that he's arbitrarily ruled that solution out).
@BenjaminLindley Oh yeah, I agree with that. See my edit, that I typed up immediately after posting and then never hit okay on :-)
1

You can suppress the warning with another C++17 attribute, namely [[maybe_unused]]

[[nodiscard]] int MyFunction() { return 42; } int main() { [[maybe_unused]] auto v = MyFunction(); return 0; } 

This way you also avoid the confusing dependency to std::tuple which comes with std::ignore, even CppCoreGuidelines is openly recommending to use std::ignore for ignoring [[nodiscard]] values:

Never cast to (void) to ignore a [[nodiscard]]return value. If you deliberately want to discard such a result, first think hard about whether that is really a good idea (there is usually a good reason the author of the function or of the return type used [[nodiscard]] in the first place). If you still think it's appropriate and your code reviewer agrees, use std::ignore = to turn off the warning which is simple, portable, and easy to grep.

Looking at C++ reference, officially std::ignore is only specified to be used in std::tie when unpacking tuples.

While the behavior of std::ignore outside of std::tie is not formally specified, some code guides recommend using std::ignore to avoid warnings from unused return values of [[nodiscard]] functions.

Comments

0

cast the result to a (void *).

int main() { (void *)someFunction(); //Warning will be gone. } 

This way you "used" your result as far as the compiler is concerned. Great for when you are using a library where nodiscard has been used and you really don't care to know the result.

2 Comments

Nice , but you may have missed this comment from the Original Poster : I would not like calling it as (void)someNonCriticalFunction(), because this function is called a lot of times, it is awkward
/s/void */void/ for the proper idiom.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.