QUIP 19: [[nodiscard]] Policy

by Marc Mutz

Details

  • Number: 19
  • Title: [[nodiscard]] Policy
  • Author: Marc Mutz
  • Status: Active
  • Type: Implementation
  • Post History: 044044.html
  • Created: 19 June 2023

This QUIP aims to document how we use the [[nodiscard]] attribute in Qt.

Motivation

The [[nodiscard]] attribute can be applied to functions to cause a warning if the function’s return value is ignored (discarded). This can be used to alert the user of the function to non-sensical (list.empty();) or dangerous code (ignoring error codes).

It can also be applied to constructors (but see below) to warn about the object being ignored (QMutexLocker(&mutex) instead of QMutexLocker locker(&mutex)).

Finally, it can be applied to classes whole-sale, in which case it applies to all functions returning that type, Qt or user functions, and, depending on compiler, to all constructors of the type, too.

Version History

  • Since [[nodiscard]] is a C++17 addition, Qt 5 has the Q_REQUIRED_RESULT macro, defined to platform-dependent equivalents, if any. This macro can only be used on (non-constructor) functions.
  • Since Qt 6.0, we can use [[nodiscard]] unconditionally on (non-constructor) functions and at class level (non-exported classes only). The use of Q_REQUIRED_RESULT is discouraged in Qt 6.
  • Since Qt 6.6, we can use Q_NODISCARD_CTOR to apply [[nodiscard]] to contructors, too.
  • Since Qt 6.7, we have access to C++20 [[nodiscard("reason")]] via Q_NODISCARD_X and Q_NODISCARD_CTOR_X.

Policy

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Unless mentioned otherwise, the following applies to both public and private Qt C++ APIs.

Unless mentioned otherwise, whenever [[nodiscard]] or Q_NODISCARD_CTOR are mentioned below, it SHALL be read as including Q_NODISCARD_X and Q_NODISCARD_CTOR_X respectively.

Constructors

  1. From Qt 6.6 onwards, all constructors of RAII and smart pointer classes SHALL be marked as Q_NODISCARD_CTOR. Constructors of other classes MAY be marked as Q_NODISCARD_CTOR, at author’s and reviewer’s discretion.
  2. Qt versions prior to 6.6 lack the macro and therefore can’t use [[nodiscard]] on constructors.
  3. If (1) asks to mark all constructors of a class nodiscard, then named constructors (static functions returning an instance of the class; often prefixed from~~~~() or create~~~()) SHALL be marked [[nodiscard]].

Other Functions

  1. Functions where calls that ignore the return type are most likely wrong (e.g. QList::empty(), which sounds like a modifying function, but in fact is const) SHALL be marked as [[nodiscard]] (or Q_REQUIRED_RESULT, in Qt 5).
  2. Other functions whose only side-effect is to produce the return value, esp. const member functions, MAY be marked as [[nodiscard]], at author’s and reviewer’s discretion.
  3. Functions that have side-effects SHALL NOT be marked [[nodiscard]], unless they fall into Case 1.

Whole Classes

  1. We currently do not use [[nodiscard]] on whole classes. Qt still contains such uses, but they are historic and scheduled to be replaced with Q_NODISCARD_CTOR on all the class’ constructors.
  2. [[nodiscard]] MAY be used on classes, though, provided the user documentation (in case of public API) or the commit message (in case of private API) give rationale for doing so.
  3. Due to a bug in GCC, [[nodiscard]] classes cannot be exported wholesale at the moment. If there is enough demand, this may be worked around in the future.

Formatting

  1. Syntactically, [[nodiscard]] MUST come first in the declaration, esp. before a Q_*_EXPORT macro, if any. If the function is a template, the template-initalizer MUST come first. C++ enforces both, even though some compilers are known to accept some forms of non-standard formatting, too (e.g. GCC).

    Examples:

    template <typname T>
    [[nodiscard]] int foo(const T&);
    
    [[nodiscard]] Q_CORE_EXPORT int foo(const QString&);
    
    [[nodiscard]] Q_CORE_EXPORT QVeryLongReturnTypeClassName
    bar(const QString&);
    
    Q_NODISCARD_CTOR explicit QFoo(const QBar &);

    Definitions that are not declarations SHALL NOT repeat the attribute.

  2. New code SHOULD add the [[nodiscard]] or Q_NODISCARD_CTOR as part of the line that also contains the (leading) return type.

    See point 1 for examples.

  3. When adding [[nodiscard]] or Q_NODISCARD_CTOR to declarations that pertain to already-released API, the attribute SHOULD be added on a separate line, or in a line with other attributes newly-added for the current release. This makes the header diff easier to read in API diffs at the cost of somewhat more vertical space needed for the declaration going forward.

  4. The _X variants and [[nodiscard("reason")]] SHOULD always be placed on a separate line, because they tend to be long themselves.

References