Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::max return by const&?

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;
}
like image 239
Agrim Pathak Avatar asked Sep 30 '22 00:09

Agrim Pathak


2 Answers

You have 2 issues here:

  1. Missing const qualifier in result
  2. It is dangerous to return reference to const reference parameter

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.

like image 52
ISanych Avatar answered Oct 04 '22 04:10

ISanych


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

like image 31
Yakk - Adam Nevraumont Avatar answered Oct 04 '22 02:10

Yakk - Adam Nevraumont