Frederick Virchanza Gotham
unread,Dec 15, 2022, 4:27:16 AM12/15/22You do not have permission to delete messages in this group
Sign in to report message
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to
To achieve perfect forwarding in C++, they brought in something called 'reference collapsing'. In normal circumstances, you can't make a reference to a reference, the following won't compile:
int main(void)
{
int i;
int & &&j = i;
}
But it will compile if you use a 'typedef' in between:
int main(void)
{
int i;
typedef int &T;
T &&j = i;
}
When we have an intermediate 'typedef', we apply the reference collapsing rules:
A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
(A quick way to remember this is
that two Rvalue refs make an Rvalue,
and everything else becomes Lvalue)
These reference collapsing rules were made so that we could write a template function that can receive either an Lvalue or Rvalue argument, and which can forward that argument appropriately as an Lvalue or Rvalue. For example, let's say we want to write a global function that keeps track of how many vector<int>'s have been assigned:
size_t g_count = 0u;
template<typename T, typename K>
requires same_as< remove_cvref_t<T>, vector<int> >
&& same_as< remove_cvref_t<K>, vector<int> >
void assign_vec(T &a, K &&b)
{
++g_count;
a = b;
}
This template function does what it's supposed to, it will perform an assignment-to-Rvalue if possible because the function argument "K&&" can accept either an Lvalue or Rvalue.
But of course this leaves you wondering . . . what if I want to write a template function that takes an Rvalue argument? If we write the following:
template<typename T>
void Plunder(T &&arg)
{
static remove_cvref_t<T> obj;
obj = arg;
}
The problem is that this function will, owing to reference collapsing rules, accept an Lvalue even though we don't want it to. In fact we have to add a line of code to explicitly forbid Lvalues:
template<typename T>
requires is_rvalue_reference_v<T&&>
void Plunder(T &&arg)
{
static remove_cvref_t<T> obj;
obj = arg;
}
So in this case, reference collapsing is a nuisance. Now I do realise the ship has sailed and that reference collapsing has already made it into the Standard, but for the life of me I don't know why they didn't make it so much simpler.
I mean in order to write a template function that can take either an Lvalue or an Rvalue, they could have used another symbol, like this:
template<typename T>
void Accept_Either_Lvalue_Or_Rvalue(T ^arg)
{
Perfectly_Forward_To_Some_Other_Func( forward(arg) );
}
The only reason why I use 'forward' here is because function arguments are always Lvalues, but there could have been an exception made for when the function parameter type is "T^", meaning we could get perfect forwarding quite simply with:
template<typename T>
void Accept_Either_Lvalue_Or_Rvalue(T ^arg)
{
Perfectly_Forward_To_Some_Other_Func(arg);
}
I don't know why they didn't do this instead of reference collapsing.