Const Rvalue reference becomes Const lvalue?!

Int&& i is a lvalue object, of rvalue int reference type. Below works as expected
1
2
3
4
5
6
7
void foo(int&&){}
int main()
{
    int&& j = 7;        //const int&& is that rvalue?
    foo(std::forward<int>(j));
    return 0;
}


However, changing int&& to const int&& and int to const int in std::forward will yield the error message:

error: binding ‘const int’ to reference of type ‘int&&’ discards qualifiers
foo(std::forward<const int>(j));
1
2
3
4
5
6
7
void foo(int&&){}
int main()
{
    const int&& j = 7;        //const int&& is that rvalue?
    foo(std::forward<const int>(j));
    return 0;
}
Last edited on
error: binding ‘const int’ to reference of type ‘int&&’ discards qualifiers

You've attempted to make a non-const reference to a const int. This would violate const-correctness.

The non-const reference in your case is the unnamed formal parameter to foo, and the const int is the expression std::forward<const int>(j).

The problem (compiler error) here
int& r = static_cast<int const&>(42); // wrong
Is essentially the same as the problem here:
int&& rr = static_cast<int const&&>(42); // wrong
Value category has little to do with it.

int&& i is a lvalue object

The phrase "lvalue object" is nonsense, because objects do not have value category.

It may help you to read this post:
https://www.cplusplus.com/forum/general/273175/#msg1178146
Last edited on
Thank you @mbozzi for the great explanation! The post really did help me with understanding value category!

However, to me this seems to be a constness issue:

With const it does work
1
2
3
4
5
6
7
8
#include <utility>
void foo(int&&){}
int main()
{
    int&& j = 7;        //const int&& is that rvalue?
    foo(std::forward<int>(j));
    return 0;
}


Adding const doesn't work
1
2
3
4
5
6
7
8
#include <utility>
void foo(int&&){}
int main()
{
    const int&& j = 7;        //const int&& is that rvalue?
    foo(std::forward<const int>(j));
    return 0;
}
Simply, you can't have a const ref-ref (rvalue) as move semantics mean that the value of a ref-ref can be changed as it is only a temporary.
Without full understanding of how std::forward actually works I just want to say that I don't think it's meant to be used outside templates. It's meant to be used with forwarding references (also known as universal references).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <utility>
#include <iostream>

void foo(int&) { std::cout << "int&\n"; }
void foo(int&&) { std::cout << "int&&\n"; }
void foo(const int&) { std::cout << "const int&\n"; }
void foo(const int&&) { std::cout << "const int&&\n"; }

template <typename T>
void bar(T&& j)
{
	foo(std::forward<T>(j));
}

int main()
{
	int x = 1;
	bar(x); // int&

	bar(2); // int&&

	const int y = 3;
	bar(y); // const int&
	
	// I don't know how to invoke the fourth overload 
	// without a cast but it's pretty useless anyway.
	bar(static_cast<const int&&>(4)); // const int&&
}

https://en.cppreference.com/w/cpp/language/reference#Forwarding_references
Last edited on
Yes, it is a constness issue.
mbozzi wrote:
You've attempted to make a non-const reference to a const int. This would violate const-correctness.

The non-const reference in your case is the unnamed formal parameter to foo, and the const int is the expression std::forward<const int>(j).


Consider the function call
foo(std::forward<int>(j));
The function foo's first parameter is a reference to int, and the argument std::forward<int>(j) has the matching type int. Not a problem; the parameter to foo is initialized somewhat like this:
1
2
int&& first_argument_to_foo = std::forward<int>(j);
int&& first_formal_parameter_to_foo = first_argument_to_foo // okay 


Now consider the incorrect function call
foo(std::forward<const int>(j));
In this case, foo's first parameter is (still) a reference to int. But this time the argument std::forward<int>(j) has the type int const. This doesn't work: the parameter to foo would be initialized like so
1
2
int const&& first_argument_to_foo = std::forward<int const>(j);
int&& first_formal_parameter_to_foo = first_argument_to_foo // wrong 

Which is illegal, because the first_argument_to_foo is a reference to const. The compiler can add but not throw away that const.

It is wrong for the same reason that this is wrong:
1
2
3
4
5
6
int f(int&) {};
int main() 
{ 
  int const x = 42; 
  f(x); 
}

Or that this is wrong:
1
2
int const y = 42;
int& x = y;  


In this case the calls to std::forward are a red herring. Here std::forward is equivalent to std::move.
Last edited on
Topic archived. No new replies allowed.