You should indeed continue to make your variables const as that is good practice (called const-correctness) and it also helps when reasoning about code - even while creating it. A const object cannot be moved from - this is a good thing - if you move from an object you are almost always modifying it to a large degree or at least that is implied (since basically a move implies stealing the resources owned by the original object) !
From the core guidelines:
You can’t have a race condition on a constant. It is easier to reason
about a program when many of the objects cannot change their values.
Interfaces that promises “no change” of objects passed as arguments
greatly increase readability.
and in particular this guideline :
Con.4: Use const to define objects with values that do not change
after construction
Moving on to the next, main part of the question:
Is there a solution that does not exploit NRVO?
If by NRVO you take to include guaranteed copy elision, then not really, or yes and no at the same. This is somewhat complicated. Trying to move the return value out of a return by value function doesn't necessarily do what you think or want it to. Also, a "no copy" is always better than a move performance-wise. Therefore, instead you should try to let the compiler do it's magic and rely in particular on guaranteed copy elision (since you use c++17). If you have what I would call a complex scenario where elision is not possible: you can then use a move
combined with guaranteed copy elision/NRVO, so as to avoid a full copy.
So the answer to that question is something like: if you object is already declared as const, then you can almost always rely on copy-elision/return by value directly, so use that. Otherwise you have some other scenario and then use discretion as to the best approach - in rare cases a move
could be in order(meaning it's combined with copy-elision).
Example of 'complex' scenario:
std::string f() {
std::string res("res");
return res.insert(0, "more: ");//'complex scenario': a reference gets returned here will usually mean a copy is invoked here.
}
Superior way to 'fix' is to use copy-elision i.e.:
return res;//just return res as we already had that thus avoiding copy altogether - it's possible that we can't use this solution for more *hairy/complex* scenarios.
Inferior way to 'fix' in this example would be;
return std::move(res.insert(0, "more: "));
move_from
method. In debug builds you could add an assertion to prevent objects from being used after being moved from. But honestly, I would simply drop theconst
and be done with it. – Requirementunique_ptr
to implmutable
, or move construct the impl, as the element theunique_ptr
points to does not "inherit" the constness. Both things are kinda janky. – CynicismW<Cake> cake
instead ofCake const cake
(this could be mitigated by making the held instancemutable
). Second,auto
is out the window. Third, it takes away optimisation opportunities for the compiler because the held instance is no longerconst
(it merely put on a const-looking dress). – Orvahauto cake = bake_cake(...); const auto& cake_r = cake;
? Then, you can usecake_r
during all the code except of that return statement, where you can employstd::move
with the desired effect. – PodagraConst<Cake> cake
versusconst Cake cake
. For "second", CTAD might replaceauto
:Const cake = makeCake();
versusconst auto cake = makeCake();
. – Sapidconst
is also lost. (mutate through const reference is still possible whereas mutate const object is UB). – Sapidstd::move
is else used when "possible"), and only apply toreturn cake
anyway. not to last usage. – Sapidstd::move
it. – OrvahCake(const Cake&&)
withmutable
flag to prevent releasing resource (but not compatible with stdandard containers/smart pointers). – Sapidconst &&
parameter. But you still lose const-related optimization this way. – Decimateconst&&
has been humming around in my head as well, but I'm not sure this can of joy should be opened. Ever. – Orvahconst
effectively results in more optimized assembly in comparison with non-const object, when only const member functions are called for it? – Podagraload
. infoo(const int&); const int i = 42; foo(i); return i;
we know we return 42; inint i = 42; foo(i); return i;
we have to reloadi
which might have changed infoo
. – Sapidint not_const = 42; [](const int& i){++const_cast<int&>(i);}(not_const);
is legal. – Sapidconst
correctness if your variable can be modified. Also, if a variable is declaredconst
, then a possible optimization would be to move it into read-only memory and assume that it will never change. So moving from it, would make absolutely no sense at all. – Smackdab