3

I have an issue with clang-tidys recommendation

Clang-tidy: arg is passed by value and only used once; consider moving it.

So the code is essentially this:

struct S{
    kinda_heavy obj;
    S(kinda_heavy heavy):obj(heavy){}
    void do_stuff();
};

I'm expecting the caller to use an x-value argument.

S app = S(get_kinda_heavy());

Or maybe the caller will use it like

{
    auto env = get_kinda_heavy();
    S s{env};
    s.do_stuff();
}

I'm under the impression no copying of env will take place here under normal circumstances, i.e the caller will understand if a copy is being made or not.

I'm hoping CTidys comment isn't applicable to me.

My reason for asking is I don't want to sprinkle my code with hints that aren't useful for the reader nor the compiled code.

Clarification for me: I think I'm asking; under what circumstance is this a zero copy operation?

Of course, the kinda_heavy has both move and copy CTOR and assignment.

11
  • -> S(kinda_heavy /*&&*/ heavy):obj(sd::move(heavy)){} Commented May 30 at 18:51
  • @Jarod42 Indeed, I don't care for that if its still moving. Commented May 30 at 18:53
  • S s{env}; does a copy, you have to use S s{std::move(env)};. Commented May 30 at 19:01
  • 2
    You are in good company. For funsies (not real code) on a toy project, I used Foo&& style parameter everywhere, and I added unary operator+ to copy my Foo, and unary operator~ to move my Foo. I thought it was helpful, and made the code more succinct. But outside of the privacy of my home computer, it would get me arrested by the operator overload abuse police. Commented May 30 at 19:06
  • 1
    @Eljay If you need me to, I'll try my darnedest to appear in your defense, should your indulgence ever be prosecuted=) These are crimes we all have committed. Commented May 30 at 19:11

1 Answer 1

5

In the first case:

S app = S(get_kinda_heavy());

The temporary will bind to heavy in the constructor object (no move, no copy) and then you copy it in the initialization statement:

S(kinda_heavy heavy):obj(heavy){}

If there was an explicit move here as clang tidy suggests, then the 1 copy would turn into 1 move:

S(kinda_heavy heavy):obj(std::move(heavy)){}

That optimization was purposefully not added to the design so that there was no possibility of a double move. In more complicated code it is possible for heavy to be used twice (since it has a name). And we did not wish to ask the compiler to prove that heavy would only be used once, and perform an implicit move in that case. Since the original design, implicit moves have been added in more places. Imho if we add too many more, the design will become too clever by half and nobody will be able to remember the rules (if we're not there already).

In the second case:

auto env = get_kinda_heavy();
S s{env};

There is a copy when sending env to bind to the argument constructor. And then there is a second copy in your initialization statement:

S(kinda_heavy heavy):obj(heavy){}

If you take clang-tidy's suggestion, the second copy would turn into a move.

under what circumstance is this a zero copy operation?

In your second case, there is no avoiding the copy of env without an explicit move:

auto env = get_kinda_heavy();
S s{std::move(env)};

Presumably the copy here is necessary so that the client does not see env change value. In any event this copy is under the client's control. If the client prefers a move here he can do so explicitly.

As you have it coded, you have a copy in the initialization statement:

S(kinda_heavy heavy):obj(heavy){}

You can turn that into a move with clang-tidy's suggestion:

S(kinda_heavy heavy):obj(std::move(heavy)){}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, but as you probably have surmised, the As you have it coded, you have a copy in the initialization statement:. No I didn't tell it to make a copy, I told it to use value semantics. If this was c++98/03, no worries. I'm hoping for a divination/optimization that just doesn't exist I suppose.
You suppose correctly. That optimization was purposefully not added to the design so that there was no possibility of a double move. In more complicated code it is possible for heavy to be used twice (since it has a name). And we did not wish to ask the compiler to prove that heavy would only be used once, and perform an implicit move in that case. Since the original design, implicit moves have been added in more places. Imho if we add too many more, the design will become too clever by half and nobody will be able to remember the rules (if we're not there already).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.