I'd like to find the max Foo
and call inc()
on it, which is a non-const method. Of course in finding the max, I don't want to create any copies or moves, i.e. I don't want Foo foo = std::max(foo1, foo2)
. I tried writing my own max, and g++ insists I 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;
}
You have 2 issues here:
In such case:
Foo& foo = std::max(Foo(6), Foo(7));
compiler will construct temporary objects for parameters before function call and will destroy them after function call - so you will end up with reference to garbage. Of course if you will always use existing objects it will work - but it is easy to forget about such limitations.
You could remove const from parameters which will resolve both issues and it should be ok for you as you intend to modify object anyway.
template<typename T>
T my_max(T&& f1, T&& f2) {
return std::forward<T>(f1 > f2 ? f1 : f2);
}
the above is relatively solid, and will do what you need. It does require the two parameters to have the same r/l/const ness, which std::max
does not. Which is why max
uses const&
.
A far more complex version that finds the common reference category can be written, but it can act in surprising ways.
So not be fooled by lack of &
in return value above: in your use case, the above returns a reference. If passed rvalues it returns a value.
Here is an attempt at a super_max
that, if passed lvalues of the same type, returns an lvalue. If passed two different types, or an rvalue, returns a copy:
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);
}
it also only uses <
, and prefers the left hand side on a tie.
live example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With