C++ 模板函数中的左值和右值
C++ lvalues and rvalues in template functions
我从 http://www.cplusplus.com/reference/utility/forward:
中抽取样本
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
打印
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
但是如果我想制作 重载 模板,就像这样:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T>
void overloaded (T&& x) {std::cout << "[rvalue]";}
它打印
calling fn with lvalue: [rvalue][rvalue]
calling fn with rvalue: [rvalue][rvalue]
有什么方法可以使用模板函数来推导我传递给函数的对象是什么?
是的,这是可能的,只需将 const
添加到您的第二个重载:
template<typename T>
void overloaded (const T& x);
template<typename T>
void overloaded (const T&& x);
// ^^^^^
之所以需要 const
是为了使 x
不是转发参考。转发引用非常贪婪,如果您不传递完全相同的类型(包括任何 cv 限定符),那么将选择转发引用重载。
在你的情况下,因为你没有将 const
对象传递给 overload
,所以第二个重载总是更好的匹配。
但是如果你在那里添加一个 const
,那么它就不再是转发引用并且只能接受右值而不接受左值,因此不会更好地匹配左值但会比 const&
重载更好地匹配任何右值。
如果您需要从 x
移动,那么您将不得不做其他事情。删除转发引用中的 const&
重载和分支,无论您有右值还是左值:
template <typename T> void overloaded(T &&x) {
if /*constexpr*/ (std::is_lvalue_reference_v<T>)
std::cout << "[lvalue]";
else
std::cout << "[rvalue]";
}
注意:如果您执行对某个分支或其他分支无效的特定操作,则需要使用 if constexpr
。
只要函数模板参数的类型为 "rvalue reference to a type template parameter",就像 template<typename T> void overloaded(T&& x);
中的 x
,它就会变成 "forwarding reference" 又名 "universal reference"。它们遵循特殊规则,即它们可以匹配左值或右值参数。
当参数为右值时,T
其类型为非引用,参数类型为右值引用。
当参数为左值时,T
为左值引用类型。这是有道理的,因为第二个 "reference-collapsing rule":如果您将 &
或 &&
添加到类型别名(typedef
,用 using
定义的类型,或者类型模板参数)已经是一个引用,它形成一个引用类型,如果别名都是右值引用并且您添加 &&
或在其他三种情况下为左值引用,则它是右值引用。
所以当你调用 overloaded(x)
时 x
是类型 int
的左值,overloaded
的两个重载匹配:
template<typename T>
void overloaded (const T& x);
// Specialization void overloaded<int>(const int&);
template<typename T>
void overloaded (T&& x);
// Specialization void overloaded<int&>(int&);
但是第二个特化更适合参数类型,所以它获胜。
为了防止这种情况,我会使用 SFINAE 技术将第二个 overloaded
限制为仅非引用类型。
#include <type_traits>
// As before:
template<typename T>
void overloaded (const T& x);
template<typename T>
auto overloaded (T&& x) -> std::enable_if_t<!std::is_reference<T>::value>;
现在给定任何左值参数,第二个模板将推导出 T
为左值引用类型,无法将其替换为 return 类型,然后模板将因重载而被忽略分辨率,以便可以改用第一个重载。
第二个 overloaded
模板不适用于右值,而是转发引用。它倾向于非常积极地接听电话,因为它会准确地推断出 const-enss 和 value-ness,使其在几乎所有情况下都是首选(您的 int 调用带有左值但它不是 const,所以这就是它被调用的原因).
要完成这项工作,您需要限制该模板。当该模板匹配左值时,T
将是引用类型,因此引用折叠规则表明 T&&
将折叠为 T&
。所以我们可以这样做:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T, std::enable_if_t<!std::is_reference<T>::value, int> = 0>
void overloaded (T&& x) {std::cout << "[rvalue]";}
如果你这样做,那么你应该得到预期的输出。
我从 http://www.cplusplus.com/reference/utility/forward:
中抽取样本// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
打印
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
但是如果我想制作 重载 模板,就像这样:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T>
void overloaded (T&& x) {std::cout << "[rvalue]";}
它打印
calling fn with lvalue: [rvalue][rvalue]
calling fn with rvalue: [rvalue][rvalue]
有什么方法可以使用模板函数来推导我传递给函数的对象是什么?
是的,这是可能的,只需将 const
添加到您的第二个重载:
template<typename T>
void overloaded (const T& x);
template<typename T>
void overloaded (const T&& x);
// ^^^^^
之所以需要 const
是为了使 x
不是转发参考。转发引用非常贪婪,如果您不传递完全相同的类型(包括任何 cv 限定符),那么将选择转发引用重载。
在你的情况下,因为你没有将 const
对象传递给 overload
,所以第二个重载总是更好的匹配。
但是如果你在那里添加一个 const
,那么它就不再是转发引用并且只能接受右值而不接受左值,因此不会更好地匹配左值但会比 const&
重载更好地匹配任何右值。
如果您需要从 x
移动,那么您将不得不做其他事情。删除转发引用中的 const&
重载和分支,无论您有右值还是左值:
template <typename T> void overloaded(T &&x) {
if /*constexpr*/ (std::is_lvalue_reference_v<T>)
std::cout << "[lvalue]";
else
std::cout << "[rvalue]";
}
注意:如果您执行对某个分支或其他分支无效的特定操作,则需要使用 if constexpr
。
只要函数模板参数的类型为 "rvalue reference to a type template parameter",就像 template<typename T> void overloaded(T&& x);
中的 x
,它就会变成 "forwarding reference" 又名 "universal reference"。它们遵循特殊规则,即它们可以匹配左值或右值参数。
当参数为右值时,T
其类型为非引用,参数类型为右值引用。
当参数为左值时,T
为左值引用类型。这是有道理的,因为第二个 "reference-collapsing rule":如果您将 &
或 &&
添加到类型别名(typedef
,用 using
定义的类型,或者类型模板参数)已经是一个引用,它形成一个引用类型,如果别名都是右值引用并且您添加 &&
或在其他三种情况下为左值引用,则它是右值引用。
所以当你调用 overloaded(x)
时 x
是类型 int
的左值,overloaded
的两个重载匹配:
template<typename T>
void overloaded (const T& x);
// Specialization void overloaded<int>(const int&);
template<typename T>
void overloaded (T&& x);
// Specialization void overloaded<int&>(int&);
但是第二个特化更适合参数类型,所以它获胜。
为了防止这种情况,我会使用 SFINAE 技术将第二个 overloaded
限制为仅非引用类型。
#include <type_traits>
// As before:
template<typename T>
void overloaded (const T& x);
template<typename T>
auto overloaded (T&& x) -> std::enable_if_t<!std::is_reference<T>::value>;
现在给定任何左值参数,第二个模板将推导出 T
为左值引用类型,无法将其替换为 return 类型,然后模板将因重载而被忽略分辨率,以便可以改用第一个重载。
第二个 overloaded
模板不适用于右值,而是转发引用。它倾向于非常积极地接听电话,因为它会准确地推断出 const-enss 和 value-ness,使其在几乎所有情况下都是首选(您的 int 调用带有左值但它不是 const,所以这就是它被调用的原因).
要完成这项工作,您需要限制该模板。当该模板匹配左值时,T
将是引用类型,因此引用折叠规则表明 T&&
将折叠为 T&
。所以我们可以这样做:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T, std::enable_if_t<!std::is_reference<T>::value, int> = 0>
void overloaded (T&& x) {std::cout << "[rvalue]";}
如果你这样做,那么你应该得到预期的输出。