std::make_tuple
doesn't take an rvalue reference to a T
, contrary to what it seems; it takes a universal reference to a T
(T&&
). If universal reference is new to you, let me explain.
The definition of make_tuple
looks more or less like this:
template<typename... Ts>
std::tuple<Ts...> make_tuple(Ts&&... ts){
// ...
}
But for purposes of explanation, I am going to refer to make_tuple
like this:
template<typename T>
std::tuple<T> make_tuple(T&& t){
// ...
}
Using type deduction, when make_tuple
is passed an rvalue (lets say an int&&
), the type deduced for T
is int
, since make_tuple takes a T&& and it was passed an rvalue. The definition of make_tuple
(with T
deduced) now looks like this:
std::tuple<int> make_tuple(int&& t){
// ...
}
Here is where things get confusing: if make_tuple
is passed an lvalue int
, what should T
be deduced as? The compiler deduces T
as int&
and uses something called reference collapsing.
Reference collapsing basically states that if the compiler forms a reference to a reference and one of them is lvalue, then the resulting reference is lvalue. Otherwise else, it is an rvalue reference.
The definition of make_tuple
(with T
deduced) now looks like this:
std::tuple<int&> make_tuple(int& && t){
// ...
}
Which collapses to:
std::tuple<int&> make_tuple(int& t){
// ...
}
So, back to your failed example:
std::tuple<int, int> fraction = make_tuple<int, int>( x, y );
Let's see what make_tuple
looks like:
// since you explicitly called make_tuple<int,int>(), then no deduction occurs
std::tuple<int,int> make_tuple(int&& t1, int&& t2){ // Error! rvalue reference cannot bind to lvalue
// ...
}
Now it becomes clear why your example doesn't work. Since no reference collapsing occured, then T&&
stayed int&&
.
A revised version of your code should look something like this:
auto fraction = std::make_tuple(x,y);
I hope I explained this well.