Is it ok to use std::ignore in order to discard a return value of a function to avoid any related compiler warnings?
Asked Answered
P

2

19

I know that you can use static_cast<void>, but it just seems too verbose for me, and not reflecting the original intent that I want to discard a return value, not to cast it to anything.

Recently I stumbled upon std::ignore, which can accept a value of any type, the name is clear and readable, and to me it seems fitting.

I know that the initial intent was to use std::ignore alongside with std::tie to discard any unwanted values, but I guess the original intent of static_cast was to actually cast values for some better reasons than discarding values so the compiler won't complain.

So, is it OK to use std::ignore for the purpose I described in the question?

For example:

std::ignore = std::transform(...);
Procryptic answered 28/9, 2023 at 13:32 Comment(13)
As far as I know std::ignore is only defined in terms of its use in std::tie. Strictly speaking, I think it would have to be std::tie(std::ignore) = std::tie(std::transform(...));.Coexecutor
Your usage looks valid. @FrançoisAndrieux see here - the first example is not related to std::tie (std::ignore = dontIgnoreMe();).Fideism
@Fideism See the top of the page you linked : "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." cppreference agrees isn't specified. It'll probably work for any given compiler, but it seems to technically be unspecified.Coexecutor
@FrançoisAndrieux I was just about to edit my comment. I agree it is not clearly specified. But the example does use it in that way. I thought the cppreference examples are based on firm specifications. The first example is described as "Demonstrates the use of std::ignore together with a [[nodiscard]] function.".Fideism
@Fideism It is a wiki after all. It's usually pretty accurate but there are no guaranties. Checking the latest standard, you can search for ignore here and (besides the stream function) it only appears as part of tuple creation.Coexecutor
@FrançoisAndrieux got it. I usually treat cppreference.com as a good substitution for the actual reference, but I guess it has its limits ...Fideism
Regarding using static_cast<void>, this is one case where it is generally harmless to use an explicit conversion to void like (void)my_function();. Nobody can use the result, by definition, which eliminates most of the risks related to using them. And it is a relatively well known convention, so shouldn't act as a red flag or cause for confusion or concern.Coexecutor
BTW, std::transform doesn't return a [[nodiscard]]. so std::ignore = can even be removed :-), no warnings expected (else we would prefix any expression by it as std::ignore = std::cout << "hello" :) ).Disconnect
@Disconnect not only compiler warnings are there, but also warnings from other static code analysis tools :((Procryptic
// my_lint_tool_ignore_flag might be an alternative too. (or maybe the warning is more subtle that just ignoring the return value).Disconnect
Please consider expanding the shown line of (pseudo) code into a minimal reproducible example, with the exact warnings from the compiler and code analysis tools (which ones?) you used.Hiram
while you can do it, why would you? std::transform isn't a no-discard, in fact it alters something passed to it. And, unless some mistakes were made, [[nodiscard]] functions are pure, i.e. they have no side effect and do not alter their arguments, so they are literally a no-op o.oTriptych
Just use (void). Everyone understands what that's doing and it's very idiomatic at this point.Crustaceous
R
18

Yes, it is okay. In fact, it is considered much better style by some people.

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.

- CppCoreGuidelines ES.48: Avoid casts

Note: Herb Sutter committed the guideline. With the std::ignore rule, you can consistently teach not to use casts, and don't have to make an exception for casting to void.

An argument against std::ignore is that it is only defined in terms of its effect in std::tie:

template<class... TTypes>
constexpr tuple<TTypes&...> tie(TTypes&... t) noexcept;

Returns: tuple<TTypes&...>(t...). When an argument in t is ignore, assigning any value to the corresponding tuple element has no effect.

- [utilities] std::tie

This is mostly a philosophical issue though. In every major standard library, std::ignore is implemented in such a way that you can do std::ignore = ... on its own, and this may soon be well-defined. See P2968: Make std::ignore a first-class object.

In the end, it's stylistic preference. You can use (void), static_cast<void>, or std::ignore. They are all acceptable, and which one to use is a matter of opinion. What matters is that you use a consistent style throughout your project, i.e. if you use std::ignore to discard results in one place, use it everywhere.

Rusk answered 28/9, 2023 at 14:15 Comment(0)
Y
5

In theory, std::ignore's operator= could be implemented as:

[[nodiscard]] auto& operator=(auto&&...) const { return *this; }

So instead of suppressing compiler warnings, assigning to std::ignore could cause a warning.

And std::tuple's operator= could do something like this, to suppress the warning when std::ignore is part of a tuple:

(static_cast<void>(std::get<I>(*this) = std::get<I>(rhs)), ...);

In practice, no implementation defines std::ignore like this.

Whether it is OK to use std::ignore = depends on whether you want to rely on the fact that no implementation uses [[nodiscard]] in the definition of std::ignore. I, personally, would rather not rely on this undocumented fact.

Younts answered 28/9, 2023 at 18:6 Comment(1)
You might as well consider a hypothetical implementation that = deletes the operator = for std::ignore, and wraps the assignment in std::tuple in an if constexpr (!std::is_same_v<decltype(std::ignore), […]>).Moll

© 2022 - 2024 — McMap. All rights reserved.