I have a list of obj objects which each have an eval() method, returning a double. I'd like to get the maximum value of all of these. So far, I've always done
double maxval = std::numeric_limits<double>::lowest();
for (const auto &obj: objs) {
maxval = std::max(maxval, obj->eval());
}
I was wondering if there's a more elegant solution where you don't have to set the initial value -inf, something like Python's
max_value = max(obj.eval() for obj in objs)
perhaps. Note than eval() may be expensive, so I only want to eval() once per obj.
Any hints? Bonus points for readability.
Some <ranges> nice things (C++20 only):
#include <ranges>
const auto maxval = std::ranges::max_element(objs, std::less<>{}, &ObjType::eval);
if (maxval != objs.cend())
doStuffWith(*maxval);
where ObjType is the type of the sequence elements. The last check could also be on the size of the container as maxval would certainly be a dereferencable iterator when the sequence isn't empty, e.g.
if (!objs.empty()) ; // ...
Note however that as @NathanOliver has pointed out, this invokes eval() 2N-2 times. Here is a custom template that would call eval() exactly N times:
#include <optional>
#include <functional>
#include <type_traits>
template <class Range, class Cmp = std::less<>, class Proj = std::identity>
auto maxValue(const Range& rng, Cmp pred = Cmp{}, Proj p = Proj{})
{
using std::begin;
using std::end;
using ValueType = std::remove_cvref_t<std::invoke_result_t<Proj,
decltype(*begin(rng))>>;
auto first = begin(rng);
const auto last = end(rng);
if (first == last)
return std::optional<ValueType>{};
auto result = std::invoke(p, *first);
for (++first; first != last; ++first)
result = std::max(std::invoke(p, *first), result, pred);
return std::optional{result};
}
It doesn't return an iterator, but the resulting value - wrapped into a std::optional in case the range is empty (then the result is std::nullopt). Usage for a type Test with member function Test::eval() would be like this:
const auto max = maxValue(myContainer, std::less<>{}, &Test::eval);
Second and third argument have sensible defaults, so for primitive types and the like they could be left out.
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