Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using max<int> as a predicate breaks in C++11

Tags:

c++

c++11

c++14

In C++03 the following code works fine:

int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);

    std::vector<int> v2;
    v2.push_back(2);
    v2.push_back(3);
    v2.push_back(4);

    std::transform(v.begin(), v.end(), v2.begin(), v2.begin(), std::max<int>);
    return 0;
}

In C++11 this doesn't work because it added an overload for std::max that contains an initializer_list. Therefore, you have to use a very ugly cast to choose the correct overload:

static_cast<const int& (*)(const int&, const int&)>(std::max)

I have a few questions.

  • Why did the standard committee decide to do this knowing it would (probably) break existing code and force the user to create an ugly cast?
  • Are future standards of C++ going to attempt to alleviate this problem?
  • What is a workaround?
like image 468
user4112979 Avatar asked Oct 06 '14 11:10

user4112979


3 Answers

If you are doing this sufficiently frequently, you might want to write a transparent functor wrapper:

struct my_max {
    template<class T>
    const T& operator()(const T& a, const T& b) const{
        return std::max(a, b);
    }
};

Then you can simply do

std::transform(v.begin(), v.end(), v2.begin(), v2.begin(), my_max());

whenever you need it, rather than writing a lambda or a cast each time. This is basically the same idea as the transparent operator functors - let the template arguments be deduced at the actual call site rather than explicitly specified when you create the functor.

If you want to make this fancier, you can even have operator() take heterogeneous types and add perfect forwarding and use trailing return types:

struct my_max {
    template<class T, class U>
    constexpr auto operator()( T&& t, U&& u ) const
      -> decltype(t < u ? std::forward<U>(u) : std::forward<T>(t)){
        return t < u ? std::forward<U>(u) : std::forward<T>(t);
    }
};

In C++14, this is simplified to

struct my_max {
    template<class T, class U>
    constexpr decltype(auto) operator()( T&& t, U&& u ) const{
        return t < u ? std::forward<U>(u) : std::forward<T>(t);
    }
};
like image 124
T.C. Avatar answered Oct 15 '22 21:10

T.C.


What is a workaround?

A lambda is probably the most readable and useful for predicates and comparators:

std::transform(v.begin(), v.end(), v2.begin(), v2.begin(),
               [] (int a, int b) {return std::max(a,b);} );

You might want to check out T.C.s functor if you need it more often. Or, with C++14:

auto max = [] (auto&& a, auto&& b) -> decltype(auto)
  {return a > b? std::forward<decltype(a)>(a) : std::forward<decltype(b)>(b);};

Why did the standard committee decide to do this knowing it would (probably) break existing code and force the user to create an ugly cast?

The only explanation is that they found the new overload to bring enough joy to compensate the breaking of existing code and the need for workarounds in future. You could just use std::max_element instead of this new overload, so you trade the syntax sugar for passing std::max-specializations as predicates for the syntax sugar of finding the maximum element within a couple of variables without explicitly creating an array for it.

Basically

std::transform( ..., std::max<int> );
// <=>
std::transform( ..., [] (int a, int b) {return std::max(a,b);} );

vs

int arr[] {a,b,c,d}; // You don't have an array with a,b,c,d included consecutively yet
int maximum = *std::max_element( std::begin(arr), std::end(arr) ); // ensure arr non-empty!
// <=>
auto maximum = std::max({a, b, c, d});

Maybe it does compensate? On the other hand, you barely ever need the latter.

Are future standards of C++ going to attempt to alleviate this problem?

I don't think so. Apparently, the standard committee really doesn't like to remove recently introduced features. I don't really see that much of a problem either; The lambda does the job.

like image 40
Columbo Avatar answered Oct 15 '22 22:10

Columbo


Although I've accepted T.C's answer which provides a comprehensive breakdown, as stated in a comment, I want to mimic the transparent comparator functor for class templates like std::less. This answer is provided for critique by others incase there's anything wrong with the syntax.

template <typename T = void>
struct my_max;

template <>
struct my_max<void> {
    template<class T, class U>
    constexpr decltype(auto) operator()( T&& t, U&& u ) const {
        return t < u ? std::forward<U>(u) : std::forward<T>(t);
    }
};
like image 3
user4112979 Avatar answered Oct 15 '22 22:10

user4112979