Date: Thu, 10 Apr 2025 11:43:20 +0100
On Thu, Apr 10, 2025 at 11:16 AM Andrey Semashev wrote:
>
> This transformation may affect correctness and ABI (e.g. when
> DistributeNode is a template that gets instantiated on a reference
> argument type before the transformation and a non-reference type after).
> I don't think this would be acceptable.
Sometimes DistributeNode gets passed an Lvalue, and sometimes it gets
passed an Rvalue. I don't think this is a big deal. It's commonly
accepted that the L-value-accepting and R-value-accepting forms of a
function shall do exactly the same thing except that the latter might
be more efficient and that it might leave the object in an invalid
state (e.g. if it plunders its resources).
> On the whole general idea, I don't see much benefit. You're effectively
> saving typing one std::forward call per branch (which is often one), and
> in exchange make the forwarding rules more obscure. It's not clear what
> the "last mention" means, exactly. For example, do these count as
> "mentions" and are these eligible to implicit forwarding?
>
> ProcessNodePtr(&arg);
> ProcessNodeRef(*&arg);
> arg.NodeMethod();
Yes all of these count as a 'mention'. I realise in the first two
lines that you're only taking the variable's address, but the third
line "arg.NodeMethod()" might make the object invalid, and so we don't
want the address of an invalid object lingering around anywhere.
> if (arg1 == arg2) // user-defined operator==
> ...
>
> arg = x;
The above line is equivalent to:
arg.operator=( x );
and maybe the "operator=" is deleted for Rvalues of the class, something like:
class SomeClass {
public:
template<typename T> SomeClass &operator=(T&&) & { /* perform
assignment */ }
template<typename T> SomeClass &operator=(T&&) const & = delete;
template<typename T> SomeClass &operator=(T&&) && = delete;
}
So that line might not compile. But anyway let's say in this instance
that "operator=" can be used with Rvalues of decltype(arg). Either
way, automatic forwarding won't take place here because of the next
line:
> DistributeNode(arg); // does this still forward after assignment?
Yes, the forwarding still happens here.
> There probably are other dubious use cases. I think, I would rather
> prefer to be explicit about when a move is allowed to happen.
I'm thinking that automatic forwarding will have the total opposite
effect. Instead of it introducing "dubious" situations, I'm thinking
it will remove the possibility of human error. So long as there are no
compiler bugs, the argument gets forwarded where possible for maximal
efficiency.
>
> This transformation may affect correctness and ABI (e.g. when
> DistributeNode is a template that gets instantiated on a reference
> argument type before the transformation and a non-reference type after).
> I don't think this would be acceptable.
Sometimes DistributeNode gets passed an Lvalue, and sometimes it gets
passed an Rvalue. I don't think this is a big deal. It's commonly
accepted that the L-value-accepting and R-value-accepting forms of a
function shall do exactly the same thing except that the latter might
be more efficient and that it might leave the object in an invalid
state (e.g. if it plunders its resources).
> On the whole general idea, I don't see much benefit. You're effectively
> saving typing one std::forward call per branch (which is often one), and
> in exchange make the forwarding rules more obscure. It's not clear what
> the "last mention" means, exactly. For example, do these count as
> "mentions" and are these eligible to implicit forwarding?
>
> ProcessNodePtr(&arg);
> ProcessNodeRef(*&arg);
> arg.NodeMethod();
Yes all of these count as a 'mention'. I realise in the first two
lines that you're only taking the variable's address, but the third
line "arg.NodeMethod()" might make the object invalid, and so we don't
want the address of an invalid object lingering around anywhere.
> if (arg1 == arg2) // user-defined operator==
> ...
>
> arg = x;
The above line is equivalent to:
arg.operator=( x );
and maybe the "operator=" is deleted for Rvalues of the class, something like:
class SomeClass {
public:
template<typename T> SomeClass &operator=(T&&) & { /* perform
assignment */ }
template<typename T> SomeClass &operator=(T&&) const & = delete;
template<typename T> SomeClass &operator=(T&&) && = delete;
}
So that line might not compile. But anyway let's say in this instance
that "operator=" can be used with Rvalues of decltype(arg). Either
way, automatic forwarding won't take place here because of the next
line:
> DistributeNode(arg); // does this still forward after assignment?
Yes, the forwarding still happens here.
> There probably are other dubious use cases. I think, I would rather
> prefer to be explicit about when a move is allowed to happen.
I'm thinking that automatic forwarding will have the total opposite
effect. Instead of it introducing "dubious" situations, I'm thinking
it will remove the possibility of human error. So long as there are no
compiler bugs, the argument gets forwarded where possible for maximal
efficiency.
Received on 2025-04-10 10:43:33