I haven't found a video about const
all the things ¹, but there's at least Con.3: By default, pass pointers and references to const
s from the Cpp Core Guidelines.
There's lots of presentations and other resources suggesting to constexpr
all the things, e.g. CppCon 2017: Ben Deane & Jason Turner “constexpr ALL the Things!”², but that, as suggested in the comments, doesn't really parallel either of const
or noexcept
, because it does not alter the interface of a function, but just adds extra functionality.
But as far noexcept
goes, I don't seem to find as much "enthusiasm". The suggestions are more often skewed towards being very careful with it:
noexcept
is tricky, as it is part of the functions interface. Especially, if you are writing a library, your client code can depend on the noexcept
property. It can be difficult to change it later, as you might break existing code.noexcept
is part of the function's interface; you should not add it just because your current implementation happens not to throw. I am not sure about the right answer to this question, but I am quite confident that how your function happens to behave today has nothing to do with it…noexcept
when exiting a function because of a throw
is impossible or unacceptable from the Cpp Core Guidelines, which is way less liberal than an hypothetical noexcept
all the things.noexcept
ing a function.But isn't all that true for const
as well? When I declare a function parameter as const&
, am I not making a decision about the interface that I will not be able to revert in the future because it would break all my clients?
Though, I feel like putting const
all over the place passes the test of time more easily. Take the case of a function that was designed in the early days of C++11 to loop on standard container with read access. I suppose³ one could have designed it like this
void work(auto const& r) {
for (auto const& e : r) {
…
}
}
Why should work
have write access to r
at all? auto const& r
seemed the right thing to do, I maintain, at least in "ordinary" scenarii.
But with C++20 (or even C++14 + Range-v3), that's not true anymore, not even in "ordinary" scenarii, because one should take into account that callers might not pass containers to work
on, but views of some sort. Crucially, those views might require to mutate themselves, when looped over, as is the case for filter_view
:
std::vector<int> v;
work(v); // ok
work(v | std::views::transform(std::identity{})); // ok
work(v | std::views::filter(std::identity{})); // compile-time failure
The last line doesn't compile with the provided declaration of work
.
The solution, at least in this case, is that work
should accept auto&&
instead of auto const&
.
But doing such a change doesn't break the two lines which were // ok
in the first place, so it turns out that the ancient decision of declaring work
's r
parameter as auto const&
was not really a blood oath as much as putting noexcept
seems to be according to common lore.
(¹) But I kinda remember the title constexpr
all the things was a "spin-off" of an earlier const
all the things title from somewhere; I kind remember having heard Kate Gregory say the latter in some video; or maybe I just misremember, but I definitely think that it's a general suggestion; yes, with the due caveats, sure.
(²) With due caveats, and maybe there's some opposite opinion too, such as in Don't constexpr All the Things - David Sankel [CppNow 2021], which I haven't watched yet, but I maintain there's way more instances of do constexpr
all the things around.
(³) I've started my career in 2019, so I've never been a pre-C++17 programmer, so I'm making assumptions about the past that could be wrong.
But isn't all that true for const as well? When I declare a function parameter as
const&
, am I not making a decision about the interface that I will not be able to revert in the future because it would break all my clients?
Yes.
One needs to actually put effort into designing good interfaces. Try to approach it as: could an implementation reasonably need this power?
So if throwing is impossible because there's no way for the function's operation, not the current implementation, to fail or because you have another way to communicate the only types of failures... use noexcept
.
Likewise, if there's a possibility a function may need to return information and there's no other channel to do so... Use a pointer or reference to non-const
. But the guidelines also favor using the actual return type to return information, over out or in-out parameters, so you shouldn't find yourself in this situation as often. Almost to the point that if you think you could treat a parameter as in-out, you probably already are going so (and modifying it).
EDIT: That's how you can approach the decision. You also ask why you might feel differently about your decisions in the future in the two cases.
Suppose you were too conservative and believed you didn't need to modify a parameter, but now you do. You change the parameter to take by reference instead of reference to const
. How is the rest of your system affected? If the caller already had a modifiable value, it can pass it. If it didn't, compilation fails. This is the safest failure mode when changing code.
Now, take noexcept
. Suppose you were too conservative and added noexcept
because you believed a function did not need to throw. Now, you want to throw so you remove noexcept
. How is the rest of the system affected? It compiles. No errors. If called from another noexcept
function, previously it was perfectly safe. Now, it still compiles, but it sometimes crashes (via std::terminate
). You added a bug.