如何有意地丢弃一个带有[[nodiscard]]标记的返回值?

55

假设我有

[[nodiscard]] int foo ()
{
    return 0;
}

int main ()
{
    foo ();
}

然后

error: ignoring return value of ‘int foo()’, declared with attribute nodiscard [-Werror=unused-result]

但是如果

int x = foo ();

然后

error: unused variable ‘x’ [-Werror=unused-variable]

有没有一种干净的方式告诉编译器“我想要丢弃这个 [[nodiscard]] 值”?


3
抱歉如果我有点多管闲事,但是您为什么想要舍弃这个值呢?如果运行代码并且舍弃这个值是有意义的,那么这个函数就不应该被指定为[[nodiscard]],对吧?我可以想出两个原因来调用一个带有[[nodiscard]]的函数进行副作用,即单元测试和缓存预热。在 UT 的情况下,通常会将返回值与某些预期结果进行比较。 - Arne Vogel
1
在我的特定情况下,列表容器的erase方法返回迭代器到后续的列表节点,但是该后续节点迭代器已经为调用函数所知。 - spraff
@spraff 根据你的描述,具有副作用的函数可能不应该标记为 [[nodiscard]] - undefined
6个回答

51

将其转换为 void:

[[nodiscard]] int foo ()
{
    return 0;
}

int main ()
{
    static_cast<void>(foo());
}

这基本上是告诉编译器:“是的,我知道我要丢弃这个,我确定。


4
要是在很多年前,甚至几十年前就规定了必须将忽略的返回值转换成void类型,那该有多好啊!这样在编辑、编译、链接、运行和调试之前就能够避免很多错误了(更不用说生产环境了)! - davidbak
6
除了在原始的C语言中没有void之外,忽略某些库函数(如printf)的返回值是常见做法。要求显式丢弃这些函数的返回值将破坏当时存在的几乎所有程序,并导致一些非常不雅的代码。 - 1201ProgramAlarm

36
WG14 nodiscard proposal 讨论了允许通过转换为void来消除诊断信息的原因。它说转换为void是鼓励的(如果不规范的)方式,遵循了现有实现使用__attribute__((warn_unused_result))的做法:

[[nodiscard]]属性在实际中被广泛使用,并由Clang和GCC实现为__attribute__((warn_unused_result)), 但是在WG21标准化时以[[nodiscard]]的名称标准化。该提案选择了nidiscard作为标识符, 因为与C ++的不兼容性会导致不必要的问题。

此属性的语义严重依赖于“use”的概念,其定义由实现自行决定。然而, WG21指定的非规范性指南鼓励实现在将nodiscard函数调用用于潜在评估的弃置值表达式时发出警告诊断, 除非它是显式转换为void。这意味着,不鼓励实现执行数据流分析(就像未初始化但未使用的本地变量诊断所需的那样)。 ...

C ++的方式是static_cast<void>。请参阅C++标准草案[[dcl.attr.nodiscard]p2]:
[注:nodiscard调用是指调用先前声明为nodiscard的函数或其返回类型为可能带cv限定符的nodiscard标记类或枚举类型的函数调用表达式。 除非明确转换为void,否则不鼓励在作为潜在求值弃置值表达式时出现nodiscard调用。 在这种情况下,实现应发出警告。 通常是因为弃置nodiscard调用的返回值会产生意外的后果。 -注解结束]
这是一条注释,因此不属于规范,但基本上这就是现有实现对 __attribute__((warn_unused_result)) 所做的处理方式。另外,注意对nodiscard的诊断也是非规范性的,因此违反nodiscard的诊断并不非法,就像通过转换为void来抑制一样,它是质量实施的一部分。
请参见clang关于nodiscard、warn_unused_result的文档:

Clang支持能够诊断在可疑情况下函数调用表达式的结果被丢弃的能力。当一个函数或其返回类型被标记为[[nodiscard]](或__attribute__((warn_unused_result))),并且该函数调用出现为潜在评估的已弃值表达式未显式转换为void时,会生成一条诊断信息。


22

CppCoreGuidelines 建议使用std::ignore

不要强制转换为(void)来忽略[[nodiscard]]返回值。如果您有意想要丢弃此类结果,请仔细考虑是否真的是个好主意(通常函数或返回类型作者之所以使用[[nodiscard]],都有一个很好的理由)。如果您仍然认为这是合适的,并且您的代码评审者同意,可以使用std::ignore =关闭警告,这是简单、可移植和易于搜索的。

这几乎与另一个答案中建议使用的boost::ignore_unused相同,但超出了std::范畴。

然而,使用std::ignore还存在以下缺点:

  • 与其他辅助功能一样,需要进行实例化,花费编译时间,并在非优化调试中被调用,花费运行时间
  • std::ignore用于其他目的
  • std::ignore甚至不能保证抑制警告

"std::ignore甚至不能保证抑制警告",不过只要它使用了结果,它必须抑制结果未使用的警告...我明白std::ignore的原始目的并非如此,但显然需要它能够实现这一功能。 - underscore_d
"std::ignore甚至不能保证抑制警告",但只要它使用了结果,它必须抑制结果未使用的警告...我明白std::ignore的原始目的并非如此,但显然需要它能够起作用。 - undefined
1
@underscore_d,是的,它适用于通常能够正式理解结果被std::ignore使用的编译器警告,但对于试图在某种程度上跟踪控制流的静态分析工具可能会有问题。 - Alex Guteniev
1
@underscore_d,是的,它适用于通常的编译器警告,它正式理解到结果被std::ignore使用,但对于试图在某种程度上跟踪控制流的静态分析工具可能会有问题。 - undefined
唯一正确的答案! - undefined

21

您还可以使用其他标记标记返回的int:

[[nodiscard]] int foo ()
{
    return 0;
}

int main ()
{
    [[maybe_unused]] int i = foo ();
}

如果您有一些仅用于调试的代码需要这个值,那么这可能会很有用。


我只是用一个全局的编译指示符关闭了未使用变量的警告。没有比这个更无用的编译器警告了。哦等等,还有:未使用的参数(暗示你从签名中删除参数的标识符)! - davidbak
8
实际上,我不同意。我有意在我的应用中打开了警告,因为我不想到处都是杂乱无章的内容,除非我知道这个属性可以做什么。不同的政策,不同的方法 ;) - Matthieu Brucher
2
我更喜欢这个解决方案,而不是将其转换为void,因为我觉得后者有点不直观。我也不认为未使用的变量/参数是无用的,事实上,它有时会在大型遗留代码库中捕获bug,其中函数有数千行(是的,我知道x-)。只是我的两分钱。 - AndersK
我downvote的原因是,你实际上绑定了foo的返回值而不是丢弃它。区别在于变量的丢弃顺序。如果你绑定它,该值将在变量超出范围后被丢弃,如果你丢弃它,该值将立即被丢弃。 - hellow

12

我使用一个(空的)辅助函数“discard”

template<typename T>
void discard(const T&) {}

[[nodiscard]] int foo ()
{
    return 0;
}

int main ()
{
    discard(foo());
}

有意地舍弃一个[[nodiscard]]值。


只有在未启用对未使用函数参数的警告时才有效。 - Balázs Kovacsics

3

使用Boost库:

#include <boost/core/ignore_unused.hpp>
int main ()
{
    boost::ignore_unused(foo ());
}

boost::ignore_unused按引用传递其参数,因此参数必须是可以绑定到const引用的内容。我相信除了void之外,任何函数返回类型都应该可以在这里使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接