Avoid returning local variable declared const

#include <string>

std::string makeString();

std::string foo()
{
    const std::string bar = makeString(); 

    return bar.size() > 5 ? bar : std::string{};
}

As far as I understand the literature, returning a local variable that is declared const inhibits implicit move. It does not inhibit Named Return Value Optimization, which in turn would be inhibited by explicit move. In cases like above, NRVO cannot happen, so the compiler should fall back on implicit move.

It looks like there are rules for most of the cases in this space, except for this.

I have a correction. For the purposes of this suggestion the code example has to be written as

if (bar.size() > 5) 
{
    return bar;
}
return std::string{};

Because a ternary expression is its own construct that “returns” a value, and within that expression, it appears that the value cannot be implicitly moved from. And I suppose that it’s also impossible by construction to perform NRVO on a value through a ternary expression, considering that there’s a condition there.

But in general, judging by this c++ - Moving out of one side of a ternary operator - Stack Overflow it looks like there is compiler divergence in how ternary expressions are handled in this space, which seems like an indication that there’s potential for a rule about the types on each side of a ternary expression.

Hello @torgeir.skogen,

Let’s put aside the ternary expression, for now, to focus on your first point and make sure I have understood it correctly.

Let’s say we have the following piece of code:

#include <string>

std::string makeString();

std::string foo()
{
    const std::string bar = makeString(); 

    if (bar.size() > 5) 
    {
        return bar;
    }
    return std::string{};
}

The elision of the copy/move operations is mandatory for return std::string{}; so there is no problem.

But the elision (specifically the NRVO) is non-mandatory for return bar;. And if it does not occur, the implicit move won’t happen either because of the const-ness of the local variable. And that can be a shame, performance-wise.

So you propose to flag when a function returns a const local variable to improve the latter situation. Have I understood that correctly?

As a side note, NRVO is forbidden in constant expression and constant initialization, so in these cases returning a non-const local variable will always improve the situation, independently of the compiler used.

Cheers,
Amélie

1 Like

Yes, that is the core of this suggestion. Also, for reference, there appears to be a similar clang-tidy check https://clang.llvm.org/extra/clang-tidy/checks/performance/no-automatic-move.html, where the example code uses implicit move instead of NRVO because the function returns a different type from the local variable, where std::optional would be a good example.

Also, it seems important that this rule should not cause conflicts C++ static code analysis: A variable which is not modified shall be const qualified

Hello @torgeir.skogen,

Thank you for the additional details. I have created a ticket to add a rule about not preventing implicit moves: CPP-4480.

About your second point on ternary expressions:
On the specific example described in the Stack Overflow post, we have S5415: “std::move” should only be used where moving can happen that would raise an issue to make the user aware of what occurs behind the scene.
There may be more to do, but I currently do not see what. I need to think about it a bit more.

Thanks a lot for your feedback!

Cheers,
Amélie