为什么 std::max return by const&?
Why does std::max return by const&?
我想找到最大值 Foo
并对其调用 inc()
,这是一个非常量方法。当然,在寻找最大值时,我不想创建任何副本或移动,即我不想 Foo foo = std::max(foo1, foo2)
。我尝试编写自己的 max,g++ 坚持要我 return a const&.
#include <iostream>
class Foo
{
public:
Foo(int x) : x_(x) { std::cout << "const" << std::endl; }
Foo(const Foo& foo) : x_(foo.x_) { std::cout << "copy const" << std::endl; }
Foo(Foo&& foo) : x_(foo.x_) { std::cout << "move const" << std::endl; }
bool operator< (const Foo& foo) const { return x_ < foo.x_; }
bool operator> (const Foo& foo) const { return x_ > foo.x_; }
void inc() { ++x_; }
int x_;
};
/*
* Doesn't compile. Must return const T& or must accept non-const T&
*
template<typename T>
inline T& my_max(const T& f1, const T& f2)
{
return f1 > f2 ? f1 : f2;
}
*
*/
int main()
{
Foo foo1(6);
Foo foo2(7);
Foo& foo = std::max(foo1, foo2); //Doesn't compile. Must be const Foo&. But then next line fails
foo.inc();
std::cout << foo.x_ << std::endl;
return 0;
}
这里有 2 个问题:
- 结果中缺少 const 限定符
- 引用const引用参数return是危险的
在这种情况下:
Foo& foo = std::max(Foo(6), Foo(7));
编译器会在函数调用之前为参数构造临时对象,并在函数调用后销毁它们 - 所以您最终会引用垃圾。当然,如果您总是使用现有对象,它会起作用 - 但很容易忘记这些限制。
您可以从参数中删除 const,这将解决这两个问题,而且您应该没问题,因为您无论如何都打算修改对象。
template<typename T>
T my_max(T&& f1, T&& f2) {
return std::forward<T>(f1 > f2 ? f1 : f2);
}
以上比较扎实,会做你需要的。它确实要求两个参数具有相同的 r/l/const ness,而 std::max
则不需要。这就是 max
使用 const&
.
的原因
可以编写一个复杂得多的版本来找到共同的引用类别,但它的行为方式可能令人惊讶。
所以不要被上面的 return 值中缺少 &
所迷惑:在您的用例中,上面的 return 是一个参考。如果传递右值,它 return 是一个值。
这是一个 super_max
的尝试,如果传递相同类型的左值,return 是一个左值。如果传递了两种不同的类型,或一个右值,returns 一个副本:
template<class A, class B>
struct max_return:std::common_type<A,B>{};
template<class A>
struct max_return<A&,A&>{
using type=A&;
};
template<class A, class B>
using max_return_t = typename max_return<A,B>::type;
template<class T, class U>
max_return_t<T,U> super_max(T&& t, U&& u) {
if (t < u)
return std::forward<U>(u);
else
return std::forward<T>(t);
}
它也只使用 <
,并且更喜欢领带的左侧。
我想找到最大值 Foo
并对其调用 inc()
,这是一个非常量方法。当然,在寻找最大值时,我不想创建任何副本或移动,即我不想 Foo foo = std::max(foo1, foo2)
。我尝试编写自己的 max,g++ 坚持要我 return a const&.
#include <iostream>
class Foo
{
public:
Foo(int x) : x_(x) { std::cout << "const" << std::endl; }
Foo(const Foo& foo) : x_(foo.x_) { std::cout << "copy const" << std::endl; }
Foo(Foo&& foo) : x_(foo.x_) { std::cout << "move const" << std::endl; }
bool operator< (const Foo& foo) const { return x_ < foo.x_; }
bool operator> (const Foo& foo) const { return x_ > foo.x_; }
void inc() { ++x_; }
int x_;
};
/*
* Doesn't compile. Must return const T& or must accept non-const T&
*
template<typename T>
inline T& my_max(const T& f1, const T& f2)
{
return f1 > f2 ? f1 : f2;
}
*
*/
int main()
{
Foo foo1(6);
Foo foo2(7);
Foo& foo = std::max(foo1, foo2); //Doesn't compile. Must be const Foo&. But then next line fails
foo.inc();
std::cout << foo.x_ << std::endl;
return 0;
}
这里有 2 个问题:
- 结果中缺少 const 限定符
- 引用const引用参数return是危险的
在这种情况下:
Foo& foo = std::max(Foo(6), Foo(7));
编译器会在函数调用之前为参数构造临时对象,并在函数调用后销毁它们 - 所以您最终会引用垃圾。当然,如果您总是使用现有对象,它会起作用 - 但很容易忘记这些限制。
您可以从参数中删除 const,这将解决这两个问题,而且您应该没问题,因为您无论如何都打算修改对象。
template<typename T>
T my_max(T&& f1, T&& f2) {
return std::forward<T>(f1 > f2 ? f1 : f2);
}
以上比较扎实,会做你需要的。它确实要求两个参数具有相同的 r/l/const ness,而 std::max
则不需要。这就是 max
使用 const&
.
可以编写一个复杂得多的版本来找到共同的引用类别,但它的行为方式可能令人惊讶。
所以不要被上面的 return 值中缺少 &
所迷惑:在您的用例中,上面的 return 是一个参考。如果传递右值,它 return 是一个值。
这是一个 super_max
的尝试,如果传递相同类型的左值,return 是一个左值。如果传递了两种不同的类型,或一个右值,returns 一个副本:
template<class A, class B>
struct max_return:std::common_type<A,B>{};
template<class A>
struct max_return<A&,A&>{
using type=A&;
};
template<class A, class B>
using max_return_t = typename max_return<A,B>::type;
template<class T, class U>
max_return_t<T,U> super_max(T&& t, U&& u) {
if (t < u)
return std::forward<U>(u);
else
return std::forward<T>(t);
}
它也只使用 <
,并且更喜欢领带的左侧。