所以我最近了解了通用引用和引用折叠。
假设我有这样的 max 函数的两种不同实现。
template<class T>
T&& max(T&& a, T&& b)
{
return (a < b) ? b : a;
}
template<class T>
const T& max(const T& a, const T& b)
{
return (a < b) ? b : a;
}
Run Code Online (Sandbox Code Playgroud)
一种版本通过 const 引用获取参数,另一种版本通过通用引用获取参数。
让我困惑的是什么时候应该使用它们。主要使用 const 传递,以便您可以将临时变量绑定到它。
在 stl 中也有不同的用途。std::move 采用通用引用,而 std::max 采用 const ref。
什么情况下应该使用两者?
假设我想在返回时避免复制或在返回时保留引用。函数的 const 和非常量版本是否有意义?
Ami*_*rsh 12
第一个可能应该是:
// amended first version
template<class T>
decltype(auto) max(T&& a, T&& b) {
return (a < b) ? std::forward<T>(b) : std::forward<T>(a);
}
Run Code Online (Sandbox Code Playgroud)
否则它不适用于临时对象(最初的第一个选项因两个临时对象而失败)。
但即使是现在,使用上面的新版本,它也无法适用于右值和左值的混合:
int i = my_max(3, 5); // ok
i = my_max(i, 15); // fails
// no known conversion from 'int' to 'int &&' for 1st argument
Run Code Online (Sandbox Code Playgroud)
除此之外,第一个选项的返回值是左值引用或右值引用(带或不带 const,具体取决于参数)。将右值引用返回到临时值并不是最好的主意,如果我们立即从中复制它会起作用,但是最好按值返回,这不是第一个版本所采用的方法。
第二个 const-lvalue-ref 版本的陷阱是,如果向其发送临时值,我们将 const-lvalue-ref 返回到临时值,这很容易出现错误(好吧,不超过返回一个右值引用暂时的,但仍然)。如果你立即复制它,那就没问题,如果你通过引用获取它,你就在 UB 区域:
// second version
template<class T>
const T& max(const T& a, const T& b) {
return (a < b) ? b : a;
}
std::string themax1 = max("hello"s, "world"s); // ok
const std::string& themax2 = max("hello"s, "world"s); // dangling ref
Run Code Online (Sandbox Code Playgroud)
为了解决上述问题,考虑到左值引用情况的冗余复制成本,我们可以有另一种选择,按值返回:
// third version
template<class T1, class T2>
auto max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
Run Code Online (Sandbox Code Playgroud)
该语言本身采用了std::max的第二个选项,即获取并返回 const-ref。用户应足够小心,不要引用临时变量。
另一种选择可能是在自己的语义中支持右值和左值,并使用两个重载函数:
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
Run Code Online (Sandbox Code Playgroud)
通过这种方法,您始终可以获得 const-ref 形式的结果:如果您转到第一个,您将返回一个值并延长其生命周期,如果您转到第二个,您将 const-ref 绑定到 const-参考:
int i = my_max(3, 5); // first, copying
const int& i2 = my_max(i, 25); // first, life time is extended
const std::string& s = my_max("hi"s, "hello"s); // first, life time is extended
const std::string s2 = my_max("hi"s, "hello"s); // first, copying
const std::string& s3 = my_max(s, s2); // second, actual ref, no life time extension
Run Code Online (Sandbox Code Playgroud)
上述建议的问题在于,如果两个参数都是const lvalue-ref,它只会将您带到 lvalue-ref 版本。如果您想涵盖所有情况,则必须实际涵盖所有情况,如下面的代码所示:
// handle rvalue-refs
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
// handle all lvalue-ref combinations
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(const T& a, T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, T& b) {
return (a < b) ? b : a;
}
Run Code Online (Sandbox Code Playgroud)
实现重载并支持所有类型的左值引用(const 和非 const,包括混合)的最后一种方法,而不需要实现左值引用的 4 种组合,将基于 SFINAE(此处介绍为C++20带有约束,使用requires):
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T1, class T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
std::is_same_v<std::remove_cvref_t<T1>, std::remove_cvref_t<T2>>
auto& my_max(T1&& a, T2&& b) {
return (a < b) ? b : a;
}
Run Code Online (Sandbox Code Playgroud)
如果你能忍耐我最后一次……(希望你现在还没有陷入先生状态的困境)。我们可以在一个函数中实现这一切!并且作为一个附带好处,如果支持,甚至允许在派生和基类之间进行比较,因此以下内容可以正常工作:
A a = 1;
B b = 2; // B is derived from A
// if comparison between A and B is supported, you can do
const A& max1 = my_max(a, b); // const-ref to b
const A& max2 = my_max(a, B{-1}); // const-ref to a temporary copied from a
const A& max3 = my_max(a, B{3}); // const-ref to B{3}
Run Code Online (Sandbox Code Playgroud)
这将是使用单个函数支持此操作的代码:
template<typename T1, typename T2>
struct common_return {
using type = std::common_reference_t<T1, T2>;
};
template<typename T1, typename T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
has_common_base<T1, T2> // see code in link below
struct common_return<T1, T2> {
using type = const std::common_reference_t<T1, T2>&;
};
template<typename T1, typename T2>
using common_return_t = typename common_return<T1, T2>::type;
template<class T1, class T2>
common_return_t<T1, T2> my_max(T1&& a, T2&& b)
{
if(a < b) {
return std::forward<T2>(b);
}
return std::forward<T1>(a);
}
Run Code Online (Sandbox Code Playgroud)
上述机械可以在这里找到。
对于这个的可变版本,你可以关注这个 SO post。
| 归档时间: |
|
| 查看次数: |
1044 次 |
| 最近记录: |