user4112979
user4112979

Reputation: 322

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

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.

Upvotes: 26

Views: 2096

Answers (5)

NoSenseEtAl
NoSenseEtAl

Reputation: 30038

If you are ok with using C++20 ranges additions you can just use std::ranges::max.

std::ranges::max is not a function, but a struct so it can be passed to the algorithms.

Example:

#include <iostream>
#include <algorithm>
#include <vector>
#include <fmt/ranges.h>

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::ranges::max);
    std::cout << fmt::format("{}", v2);
}

Upvotes: 1

NoSenseEtAl
NoSenseEtAl

Reputation: 30038

If you have recent boost you can use BOOST_HOF_LIFT macro.

fmt lib in example is just for printing vector, all you need is boost

#include <type_traits>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <vector>
#include <boost/hof/lift.hpp>
#include <fmt/ranges.h>

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(), BOOST_HOF_LIFT(std::max<int>));
    std::cout << fmt::format("{}", v2);
}

Upvotes: -1

user4112979
user4112979

Reputation: 322

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);
    }
};

Upvotes: 3

T.C.
T.C.

Reputation: 137315

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);
    }
};

Upvotes: 12

Columbo
Columbo

Reputation: 60999

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.

Upvotes: 4

Related Questions