[Development] C++20 ctor-level [[nodiscard]] (was: Re: C++20 @ Qt)
Marc Mutz
marc.mutz at qt.io
Wed Jun 14 08:33:37 CEST 2023
Hi,
TL;DR:
- we should add Q_DISCARD_CTOR to all ctors of classes with class-level
[[nodiscard]], esp. RAII objects
- class-level [[nodiscard]] seems to be abused atm to get ctor-level
[[nodiscard]] semantics in C++!7
- do we want to mark (almost) all ctors as [[nodiscard]], in the future?
Long form:
Ivan implemented Q_NODISCARD_CTOR for 6.6 and I've started to roll use
of it our over the QtBase. I have some questions.
First, some terms:
I call this a class-level [[nodicard]]:
class [[nodiscard]] QSignalBlocker { ~~~~ };
This is a C++17 feature and we can (and do) use it unconditionally.
I call this a ctor-level [[nodiscard]]:
class QSignalBlocker {
public:
[[nodiscard]]
explicit QSignalBlocker(QObject *o) ~~~~;
~~~~
};
This is a C++20 feature (technically, it's a clarification, potentially
retroactively applied to C++!7, but that still means we can't rely on
it), which is why we need a macro.
I won't lecture about what the two so, you can read up on that on
cppreference.com:
- https://en.cppreference.com/w/cpp/language/attributes/nodiscard
- Paper: wg21.link/p1771
The obvious first scenario is to cause warnings for code such as
// Listing 1
QMutexLocker(&mutex);
Or any other RAII class. Clang warns about this with a class-level
[[nodiscard]]. GCC does not.
So the first rule should be to make all ctors [[nodiscard]] in classes
that are class-level [[nodiscard]].
So far so good.
Enter QScopedPointer. It currently has a class-level [[nodiscard]], but
does that actually make sense? C++!7 allows us to return QScopedPointer
from a function (guaranteed copy elision), so users can write functions
that return QScopedPointer. But it's not like use of that return value
is required to be required.
So I've come to think that this use of class-level [[nodiscard]] is wrong.
It's the only way to get the desired warning for Listing 1 in C++17
(absent P1771), on some compilers, but the semantics are wrong. Even for
pure RAII types: I might return a QMutexLocker from a function, but that
shouldn't mean that function's return value is [[nodiscard]]. It might
be perfectly ok to discard it. We don't know, yet we enforce that policy
for everyone by making QMutexLocker class-level [[nodiscard]].
In fact, what we actually want is ctor-level [[nodiscard]] in these
cases. For the time being, given we support C++17, I'm ok with having
class-level [[nodiscard]] on pure RAII classes (ie. not on smart
pointers), but come C++20, we should remove these in favour of
ctor-level [[nodiscard]]s.
Here comes the can of worms: If you accept that Listing 1 is worth
warning about, what about
// Listing 2
QSize(12, 32);
// Listing 3
QString("Hello, useless");
// Listing 4
QSlider(0, 100, this);
Does this mean that, like for most const member functions that have
since gotten [[nodiscard]] because their only side-effect is to produce
a return value, we should mark _all_ ctors [[nodiscard]], because their
only side-effect is to produce an object?
Thanks,
Marc
--
Marc Mutz <marc.mutz at qt.io>
Principal Software Engineer
The Qt Company
Erich-Thilo-Str. 10 12489
Berlin, Germany
www.qt.io
Geschäftsführer: Mika Pälsi, Juha Varelius, Jouni Lintunen
Sitz der Gesellschaft: Berlin,
Registergericht: Amtsgericht Charlottenburg,
HRB 144331 B
More information about the Development
mailing list