我想移动语义,但我得到了一个隐藏复制语义的通用参考
I want to move semantics but I get a universal reference which hides copy semantics
当我想使用函数参数 const T&
复制语义或使用函数参数 T&&
移动语义时,如何处理通用引用。后者隐藏了第一个
后面是带有代数向量运算符的示例代码。
#include <array>
#include <iostream>
template<typename T>
void neg(T &a) { for (auto &i : a) i = -i; }
// object 'a' remains available
template<typename T>
auto operator-(const T &a) { std::cout << "1\r\n"; T b = a; neg(b); return b; }
// object 'a' terminates its life
template<typename T>
auto operator-(T &&a) { std::cout << "2\r\n"; neg(a); return std::move(a); }
// make an rvalue
template<typename T1, typename T2>
auto operator+(const T1 &a, const T2 &b) { return a; }
int main()
{
std::array<int, 4> a;
auto d = -(a+a); // outputs 2 : correct
auto e = -a; // outputs 2 : I want 1
auto f = -std::move(a); // outputs 2 : correct
return 0;
}
编辑: user17732522 提出的解决方案之一(请给他投票)。
template<typename T>
auto operator-(T &&a)
{
std::cout << "2\r\n"; neg(a); return std::move(a);
}
template<typename T>
auto operator-(T &a)
{
std::cout << "1\r\n";
std::remove_cvref_t<T> b = a;
neg(b); return b;
}
int main()
{
std::array<int, 4> a;
auto d = -(a+a); // now outputs 2 : correct
auto e = -a; // now outputs 1 : correct
auto f = -std::move(a); // now outputs 2 : correct
const std::array<int, 4> b{1,2,3,4};
d = -(b+b); // now outputs 2 : correct
e = -b; // now outputs 1 : correct
return 0;
}
另一种解决方案,始终基于 user17732522 答案,是这样的:
template<typename T>
requires(!std::is_reference_v<T>)
auto operator-(T &&a);
template<typename T>
auto operator-(const T &a);
您只需从 const T&
中删除 const
。
与using U = std::array<int, 4>;
:
这里的问题是 T&&
将 T
推断为 U
,而不是 const U
,因为 main
中的 a
不是const
。并且绑定到 U&
而不是 const U&
在重载决议中被认为更好。
如果您从 const T&
中删除 const
,那么两个候选人在推导后都会有一个函数参数 U&
,因此基于此两者都不会更好。
然而,在函数模板的部分排序中,T&
模板将胜过 T&&
,因此如果函数参数是左值,将选择前者。
然而,这确实会导致函数中的 a
不会被 const
限定。您可以通过应用 std::as_const
.
从中获取 const
引用
或者,您可以使用 requires
子句或 std::enable_if
将 T
上的 T&&
模板限制为 non-reference 类型。或者您可以使用单个函数模板,并在其主体中根据 if constexpr
的引用类型决定如何操作。相关类型特征:std::is_lvalue_reference_v<T>
或 std::is_reference_v<T>
和 std::is_lvalue_reference_v<decltype(a)>
.
当我想使用函数参数 const T&
复制语义或使用函数参数 T&&
移动语义时,如何处理通用引用。后者隐藏了第一个
后面是带有代数向量运算符的示例代码。
#include <array>
#include <iostream>
template<typename T>
void neg(T &a) { for (auto &i : a) i = -i; }
// object 'a' remains available
template<typename T>
auto operator-(const T &a) { std::cout << "1\r\n"; T b = a; neg(b); return b; }
// object 'a' terminates its life
template<typename T>
auto operator-(T &&a) { std::cout << "2\r\n"; neg(a); return std::move(a); }
// make an rvalue
template<typename T1, typename T2>
auto operator+(const T1 &a, const T2 &b) { return a; }
int main()
{
std::array<int, 4> a;
auto d = -(a+a); // outputs 2 : correct
auto e = -a; // outputs 2 : I want 1
auto f = -std::move(a); // outputs 2 : correct
return 0;
}
编辑: user17732522 提出的解决方案之一(请给他投票)。
template<typename T>
auto operator-(T &&a)
{
std::cout << "2\r\n"; neg(a); return std::move(a);
}
template<typename T>
auto operator-(T &a)
{
std::cout << "1\r\n";
std::remove_cvref_t<T> b = a;
neg(b); return b;
}
int main()
{
std::array<int, 4> a;
auto d = -(a+a); // now outputs 2 : correct
auto e = -a; // now outputs 1 : correct
auto f = -std::move(a); // now outputs 2 : correct
const std::array<int, 4> b{1,2,3,4};
d = -(b+b); // now outputs 2 : correct
e = -b; // now outputs 1 : correct
return 0;
}
另一种解决方案,始终基于 user17732522 答案,是这样的:
template<typename T>
requires(!std::is_reference_v<T>)
auto operator-(T &&a);
template<typename T>
auto operator-(const T &a);
您只需从 const T&
中删除 const
。
与using U = std::array<int, 4>;
:
这里的问题是 T&&
将 T
推断为 U
,而不是 const U
,因为 main
中的 a
不是const
。并且绑定到 U&
而不是 const U&
在重载决议中被认为更好。
如果您从 const T&
中删除 const
,那么两个候选人在推导后都会有一个函数参数 U&
,因此基于此两者都不会更好。
然而,在函数模板的部分排序中,T&
模板将胜过 T&&
,因此如果函数参数是左值,将选择前者。
然而,这确实会导致函数中的 a
不会被 const
限定。您可以通过应用 std::as_const
.
const
引用
或者,您可以使用 requires
子句或 std::enable_if
将 T
上的 T&&
模板限制为 non-reference 类型。或者您可以使用单个函数模板,并在其主体中根据 if constexpr
的引用类型决定如何操作。相关类型特征:std::is_lvalue_reference_v<T>
或 std::is_reference_v<T>
和 std::is_lvalue_reference_v<decltype(a)>
.