Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Reference collapsing rules -- why not something more simple

65 views
Skip to first unread message

Frederick Virchanza Gotham

unread,
Dec 15, 2022, 4:27:16 AM12/15/22
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.

Paavo Helde

unread,
Dec 15, 2022, 5:18:41 AM12/15/22
to
15.12.2022 11:27 Frederick Virchanza Gotham kirjutas:
> To achieve perfect forwarding in C++, they brought in something called 'reference collapsing'.

These are hacks on hacks on hacks, all done in order to not break
compatibility with earlier versions, and with C, and sometimes with cpp
(C preprocessor).

Sometimes a hack finally gets resolved even if breaking compatibility
with something (e.g. >> in nested templates' end), but this is rare.

If you review all decisions in the context when they were made, they
will make more sense.

Frederick Virchanza Gotham

unread,
Dec 20, 2022, 2:50:01 AM12/20/22
to
On Thursday, December 15, 2022 at 9:27:16 AM UTC, Frederick Virchanza Gotham wrote:

> 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;
> }

That should be:

a = std::forward<K>(b);

0 new messages