C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Automatic perfect forwarding is possible and not too complicated

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Fri, 11 Apr 2025 22:56:38 +0100
Can't work safely in general. Just because an id is used last lexically within a function it does not mean that it is the last used reference to the object. A previous call could leak the reference and a later call can use it.

Similarly, implicitly moved strings can silently invalidate string_views.



On 10 April 2025 09:53:52 BST, Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]> wrote:
>Let's say I give you a code test, and I ask you to write the body of
>the following function as efficiently as possible:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> // write code here
> }
>
>This function does only one thing, it invokes the "AssimilateNode"
>function on the argument. So the best way to do this would be:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> AssimilateNode( forward<T>(arg) );
> }
>
>But now I tell you that the function must invoke three functions on
>the argument. It must normalise the node, then it must distribute the
>node, and finally it must assimilate the node. So then the code
>becomes:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode ( forward<T>(arg) );
> DistributeNode( forward<T>(arg) );
> AssimilateNode( forward<T>(arg) );
> }
>
>But of course now we have a problem here. After we have invoked
>'NormaliseNode', giving it an Rvalue, the argument might no longer be
>a valid object; it might have had its resources plundered by a new
>object. So we must change the above code to the following:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode ( arg );
> DistributeNode( arg );
> AssimilateNode( forward<T>(arg) );
> }
>
>The fact that we can only use 'forward' on the last use of the
>argument inside the body of the function, is the most major point of
>debate as to why the 'forwarding' should not be done automatically --
>it is the reason why we must explicitly write "std::forward< ... > (
>... )" in our code. Imagine for a moment if we were to use the
>trigraph operator '&&&' to indicate that a function parameter must
>undergo automatically forwarding, something like this:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // trigraph &&& means automatic forwarding
> {
> NormaliseNode (arg); // automatically forwarded
> DistributeNode(arg); // automatically forwarded
> AssimilateNode(arg); // automatically forwarded
> }
>
>We have the same problem here: If 'NormaliseNode' is invoked with an
>Rvalue, then we might be left with an invalid object (i.e. a
>plundered-from object) which then gets passed to 'DistributeNode'.
>
>So here's my idea:
>
>We can have automatic perfect forwarding if the compiler can correctly
>identify the last mention of the variable in the body of the function,
>and only perform forwarding on the last mention. What I mean is that
>when the compiler encounters the following code:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // trigraph &&& means automatic forwarding
> {
> NormaliseNode (arg);
> DistributeNode(arg);
> AssimilateNode(arg);
> }
>
>The compiler work as follows:
>Step 1 - It notices the use of the trigraph '&&&' in the function
>parameter, and so it knows to perform automatic forwarding on 'arg'.
>Step 2 - It reads through the function and identifies the last mention
>of the variable 'arg', and it applies 'std::forward< ... > ( ... )' to
>the last usage.
>Step 3 - All other mentions of the variable 'arg' don't get forwarded.
>
>So essentially the compiler takes the above function and converts from
>trigraph '&&&' to digraph '&&' as follows:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode ( arg );
> DistributeNode( arg );
> AssimilateNode( forward<T>(arg) );
> }
>
>Now of course, code can be a little more complicated if it branches.
>We could have a function body such as the following:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // trigraph &&& means automatic forwarding
> {
> NormaliseNode(arg);
>
> if ( 5 == (arg.id & 7u) ) // check if it's a nomadic node
> {
> SettleNode(arg);
> return;
> }
>
> DistributeNode(arg);
> AssimilateNode(arg);
> }
>
>So basically what the compiler has to do here is identify each
>possible return path from the function, and work backwards from the
>return path to identify the last mention of the variable. So the above
>function written with tripgraph '&&&' would be converted to digraph
>'&&' as follows:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode(arg);
>
> if ( 5 == (arg.id & 7u) ) // check if it's a nomadic node
> {
> SettleNode( forward<T>(arg) );
> return;
> }
>
> DistributeNode( arg );
> AssimilateNode( forward<T>(arg) );
> }
>
>There are some scenarios though in which the 'forwarding' can be
>wasted. Consider the following function:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // trigraph for automatic forwarding
> {
> NormaliseNode (arg);
> DistributeNode(arg);
>
> if ( NodeManager::mode != NodeManager::ModeAssimilate ) return;
>
> AssimilateNode(arg);
> }
>
>When the compiler converts this function body from trigraph '&&&' to
>digraph '&&', it changes it to:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode (arg);
> DistributeNode(arg);
>
> if ( NodeManager::mode != NodeManager::ModeAssimilate ) return;
>
> AssimilateNode( forward<T>(arg) );
> }
>
>You can see the inefficiency here: If the Node Manager is not
>currently in Assimilation Mode, then the invocation of the
>'ProcessNode' function will never forward the argument. This is where
>we as the programmer must be aware of how branching affects automatic
>forwarding. So we would have to rewrite the above trigraph function as
>follows:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // trigraph for automatic forwarding
> {
> NormaliseNode (arg);
>
> if ( NodeManager::mode == NodeManager::ModeAssimilate )
> {
> DistributeNode(arg);
> AssimilateNode(arg);
> }
> else
> {
> DistributeNode(arg);
> }
> }
>
>which the compiler would then covert to digraph as follows:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode(arg);
>
> if ( NodeManager::mode == NodeManager::ModeAssimilate )
> {
> DistributeNode( arg );
> AssimilateNode( forward<T>(arg) );
> }
> else
> {
> DistributeNode( forward<T>(arg) );
> }
> }
>
>You might think this is a little tedious and repetitive to have to
>split the function into two branches which two distinct invocations of
>the function 'DistributeNode', but we would have to do this with
>digraph references too, so it's not like the introduction of trigraph
>references are making anything more complicated here.
>
>So if trigraph references were to be adopted into the Standard, we
>would give compiler writers some time to implement them. Then after a
>few years, we could go further with it, and specify that the compiler
>must automatically improvise the branching -- specifically what I'm
>saying here is that when the compiler encounters the following code:
>
> template<typename T>
> bool ProcessNode(T &&&arg) // Trigraph for automatic forwarding
> {
> NormaliseNode (arg);
> DistributeNode(arg);
>
> if ( NodeManager::mode != NodeManager::ModeAssimilate ) return;
>
> AssimilateNode(arg);
> }
>
>then it must treat it as though it were written:
>
> template<typename T>
> bool ProcessNode(T &&arg)
> {
> NormaliseNode(arg);
>
> if ( NodeManager::mode == NodeManager::ModeAssimilate )
> {
> DistributeNode( arg );
> AssimilateNode( forward<T>(arg) );
> }
> else
> {
> DistributeNode( forward<T>(arg) );
> }
> }
>
>I realise that this last feature would be a lot more work for compiler
>vendors, and so it wouldn't be a part of the first specification for
>trigraph references. This last feature would be in a follow-up paper a
>few year after compiler vendors have had some time to implement the
>initial more-simplistic idea of trigraph references.
>--
>Std-Proposals mailing list
>Std-Proposals_at_[hidden]
>https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2025-04-11 21:56:49