C++20 [[nodiscard]] Attribute

Written by on January 17, 2021 in C++, Programming with 0 Comments

[[nodiscard]] attribute encourages the compiler to issue a warning when the return value from a function is ignored. It was introduced in C++ 17 and enhanced in C++ 20 to include a string literal that can be used as an explanation of the warning.

Let us look at different cases one by one.

Case-1: An enumeration is marked [[nodiscard]]

When a function is defined to return this enumeration, the compiler checks to see if the returned value is discarded. If it is, then the compiler is likely to generate a warning.

Look at the following code:

Case-1: Enumeration

Case-1: Enumeration

Here, the enum class “DeviceStatus” is marked [[nodiscard]], and the function “getDeviceStatus()” returns one of the elements of this enum. Next, look at the three calls to this function inside “main”. In Line 19, the return value from the function is ignored and this is caught by the compiler. In Line 22, the return value is checked and hence it is OK. In Line 27, we explicitly typecast the result to “void”, indicating that we are ignoring the return value deliberately and hence this is also acceptable (of course, why we chose to ignore is a different issue).

Here is the compiler output:

Compiler Output

Compiler Output

Case-2: A struct/class is marked [[nodiscard]]

In this case, instead of an enumeration, we have a class or struct that is marked [[nodiscard]].

Case-2: Struct/Class

Case-2: Struct/Class

In this case, the function “foo()” returns “MyType” instance by “value”, whereas the function “bar()” returns by “reference”. In Line 49, the returned value is ignored and hence this triggers a warning. Line 52 is fine. Interestingly, in Line 55, even though we are not using the return value, this does not generate any warning since the return is by “reference”. This is as per the standard.

Here is the compiler output:

Compiler Output

Compiler Output

Case-3: Constructor is marked [[nodiscard]]

C++20 allows constructors to be tagged as [[nodiscard]] (without the struct/class being so). 

Case-3: Constructor

Case-3: Constructor

Notice that there is no issue with Line 69, even though we are ignoring the function return value. This is because the struct “SpecialType” is not marked [[nodiscard]]. However, there is a problem in Line 73. Since the constructor is marked [[nodiscard]], the compiler sees that the constructed object is bound to a temporary and hence is not “used”. On the other hand, lines 76, 78 and 80 are OK since the constructor is used to instantiate “real” named objects.  

Compiler Output

Compiler Output

Case-4: A function is declared [[nodiscard]]

In this case, instead of a data type (enum/struct/class), a specific function is marked [[nodiscard]]. See the following example.

Case-4: Function

Case-4: Function

Here, the “computeSum()” function takes two arguments and returns a result based on the arguments. It is only natural that we are expected to use the returned value. So, this function is declared [[nodiscard]]. However, in Line 93, the return value is ignored, triggering a warning, as expected. Line 96, because it uses the return value, is OK.

Compiler Output

Compiler Output

This completes the different use cases for [[nodiscard]].

General Question: Should functions return a value?

Since the [[nodiscard]] attribute is about using/ignoring the return value of functions, it makes sense to ask this general question. As most of us know, this is a “design” question and not a mere “implementation” issue.

Functions return a value for one of three reasons:

a) The caller depends on the return value and is expected to use it.

void * malloc()

int std::rand()

float std::floor(float arg)

In all the above cases, we call the function only for its return value. So is it not an error to ignore the return value? Such functions need to be declared [[nodiscard]].

b) The caller “might” use the return value as “auxiliary” information. It is not essential for further computation and hence could be ignored.

int std::printf(const char *format, …)

Here the return value indicates the number of characters written, or if negative, indicates an error. Most of the time, the return value is ignored. These functions should not be marked [[nodiscard]].

c) The return value adheres to a coding convention and is intended to be used liberally, though not always. Consider the design of “fluent” APIs. 

MyObject obj;

Obj.doThis().doThat(30).doSomethingElse(“Hello”);

The above could also be written this way:

MyObject obj;

Obj.doThis();

Obj.doThat(30);

Obj.doSomethingElse(“Hello”);

Here, the use of return value promotes a stylistic convention, and is not a rigid rule. Such functions should not be marked [[nodiscard]] (in my opinion). 

Whether to declare an enum/class/struct/function as [[nodiscard]] requires careful deliberation, and once decided, C++ allows us to explicitly declare our intention. This is a useful feature and we should start using this in our code.

That is it for now. Hope you found the above discussion informative. You can download the sample code from here.

Take care and have a nice weekend!

Tags: ,

Subscribe

If you enjoyed this article, subscribe now to receive more just like it.

Subscribe via RSS Feed

Leave a Reply

Your email address will not be published. Required fields are marked *

Top