33

C++11 has function std::minmax_element which returns a pair of values. This, however, is quite confusing to handle and read, and produces an extra, later useless variable to pollute the scope.

auto lhsMinmax = std::minmax_element(lhs.begin(), lhs.end());
int &lhsMin = *(lhsMinMax.first);
int &lhsMax = *(lhsMinmax.second);

Is there a better way to do this? Something like:

int lhsMin;
int lhsMax;
std::make_pair<int&, int&>(lhsMin, lhsMax).swap(
    std::minmax_element(lhs.begin(), lhs.end()));
Jamal
  • 763
  • 7
  • 22
  • 32
Adam Hunyadi
  • 1,890
  • 16
  • 32
  • 4
    no one's mentioned c++17 yet? https://skebanga.github.io/structured-bindings/ – xaxxon Oct 27 '16 at 11:19
  • 2
    One question: Is there any efficiency advantage in using `minmax_element` over `min_element` and `max_element` together? – Saurav Sahu Oct 27 '16 at 11:33
  • 2
    @xaxxon: C++17 features are not in C++11. The question is tagged C++11. So it's an interesting tangent, but not an actual solution. – Lightness Races in Orbit Oct 27 '16 at 11:33
  • For separate discussion : I raised the above ques here :http://stackoverflow.com/questions/40283673/is-there-any-efficiency-advantage-in-using-minmax-element-over-min-element-and-m – Saurav Sahu Oct 27 '16 at 11:38
  • @LightnessRacesinOrbit hence not a real answer. – xaxxon Oct 27 '16 at 11:42
  • @SauravSahu I guess so, but I don't know the underlying algorithm. But otherwise this function would be useless... But it's an often used practice to couple values that are easier to calculate together. I now its easier to calculate cos and sin together. – Adam Hunyadi Oct 27 '16 at 11:42
  • @xaxxon: Just saying! – Lightness Races in Orbit Oct 27 '16 at 12:03
  • 2
    @AdamHunyadi Given that it's a standard **template** library, you automatically have the source for all algorithms. Mine is in `/usr/include/c++/6.2.1/bits/stl_algo.h`. `minmax_element` only iterates over the range once in there, so yes, it's more effective than separate executions of `min_element` and `max_element` in GCC's implementation. – The Vee Oct 27 '16 at 22:38
  • 1
    I'd argue that your "better...something like" code is not better simply by virtue of being longer, having more elements, and also by needlessly separating definition and initialization. – hyde Oct 28 '16 at 06:05

6 Answers6

32

With structured binding from C++17, you may directly do

auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 2
    Neat! Can you get the value pair out of the iterator pair in one line also? – davmac Oct 27 '16 at 11:29
  • 2
    @AdamHunyadi: C++1z is the "it doesn't formally exist yet" name for C++17, like how C++0x became C++11 (late!) and C++1y because C++14. – Lightness Races in Orbit Oct 27 '16 at 11:32
  • 9
    Upvoted because it's an interesting discussion point, and _will_ be a good solution, but this actually doesn't answer the question (which is about C++11). – Lightness Races in Orbit Oct 27 '16 at 11:34
  • 1
    @rubenvb http://stackoverflow.com/questions/40241335/structured-binding-to-replace-stdtie-abuse/40241520#40241520 – bolov Oct 27 '16 at 11:58
  • 1
    @rubenvb I think it boils down to terser syntax, the ability to declare and assign the variables in one statement, plus the ability to infer the variable types. – davmac Oct 27 '16 at 12:43
  • 3
    @LightnessRacesinOrbit: What will be the standard after `C++1z`? `C++2{`? :-) – celtschk Oct 27 '16 at 19:35
  • @celtschk: Heh, I actually tried to figure that out after writing my last comment. Inconclusive. I suspect C++2ẋ, but that's just me. – Lightness Races in Orbit Oct 27 '16 at 23:12
  • BTW, as of now, this syntax is not supported by clang 3.9 or gcc7snapshot ([on the Godbolt compiler explorer](https://godbolt.org/g/Y4HX4P), where I put a function that uses `std::minmax_element` on a `vector` so it's easy to look at the asm output. re: [discussion on branch prediction vs. branchless](http://stackoverflow.com/questions/40283673/is-there-any-efficiency-advantage-in-using-minmax-element-over-min-element-and-m#comment67860602_40284138). ) – Peter Cordes Oct 28 '16 at 11:04
23

To avoid polluting your scope, you could enclose the assignment in a smaller scope:

int lhsMin, lhsMax;

{
    auto it = std::minmax_element(lhs.begin(), lhs.end());
    lhsMin = *it.first;
    lhsMax = *it.second;
}

alternatively, you can use a lambda

int lhsMin, lhsMax;

std::tie(lhsMin, lhsMax) = [&]{
    auto it = std::minmax_element(lhs.begin(), lhs.end());
    return std::make_tuple(*it.first, *it.second);
}();
krzaq
  • 16,240
  • 4
  • 46
  • 61
  • 3
    It should be noted that neither will work for assigning references as in the first part of the OP. (The second part is mangled so it's hard to say but it also hints at assigning two `int&`s.) – The Vee Oct 27 '16 at 22:50
  • With the enclosing scope and `std::tie` is easier to read than a lambda I think. – ABu Oct 27 '16 at 23:27
  • Why the semicolon at the end of the first example? – Paul Oct 28 '16 at 11:30
13

This looks like enough of a common case to prompt a helper function:

template <class T, std::size_t...Idx>
auto deref_impl(T &&tuple, std::index_sequence<Idx...>) {
    return std::tuple<decltype(*std::get<Idx>(std::forward<T>(tuple)))...>(*std::get<Idx>(std::forward<T>(tuple))...);
}

template <class T>
auto deref(T &&tuple)
    -> decltype(deref_impl(std::forward<T>(tuple), std::make_index_sequence<std::tuple_size<std::remove_reference_t<T>>::value>{})) {
    return deref_impl(std::forward<T>(tuple), std::make_index_sequence<std::tuple_size<std::remove_reference_t<T>>::value>{});
}

// ...

int lhsMin;
int lhsMax;
std::tie(lhsMin,lhsMax) = deref(std::minmax_element(lhs.begin(), lhs.end()));

index_sequence is C++14, but a full implementation can be made in C++11.

Note: I'd keep the repeated decltype in deref's return type even in C++14, so that SFINAE can apply.

See it live on Coliru

Community
  • 1
  • 1
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Would SFINAE not apply anyway? – Lightness Races in Orbit Oct 27 '16 at 11:42
  • @LightnessRacesinOrbit I re-checked just to be sure and can confirm: since nothing restricts `T` in the function's signature, I get a hard error when instantiating the function's body. See [this quickly hacked test](http://coliru.stacked-crooked.com/a/390744d8f0deac2e). – Quentin Oct 27 '16 at 11:44
  • Hmm, guess SFINAE doesn't work how I thought it did :( – Lightness Races in Orbit Oct 27 '16 at 12:05
  • I dont understand, doens't std::tie bind straight to a pair? – Viktor Sehr Oct 27 '16 at 12:33
  • 2
    @ViktorSehr what do you mean ? `std::tie` returns a `std::tuple`. However, all `std::tuple` metafunctions, such as `std::tuple_size`, work with `std::pair` too. – Quentin Oct 27 '16 at 12:36
  • The example on cppreference demonstrates that it works with `std::pair` out of the box http://en.cppreference.com/w/cpp/utility/tuple/tie – StoryTeller - Unslander Monica Oct 27 '16 at 16:25
  • 1
    @StoryTeller it does, but the types of the variables must match the types of the elements of the pair. The problem here is that OP wants the values that the iterators refer to, not the iterators themselves. You can use `std::tie` and bind to the pair, but the pair contains the wrong values. – davmac Oct 27 '16 at 16:32
  • If anything I'd want to wrap `std::minmax_element`, into a function that returned an `optional< pair >` – Yakk - Adam Nevraumont Oct 27 '16 at 20:14
  • `std::tuple_size` will fail for lvalue tuples, if you happen to care about those. It needs `std::remove_reference[_t]`. This also has the same issue as the other `deref` answer, in that prvalue-yielding iterators will cause dangling references through `std::forward_as_tuple`. – Xeo Oct 27 '16 at 23:06
  • @Xeo well-spotted. This should be fixed AFAICT. – Quentin Oct 29 '16 at 10:30
4

I'd just be more direct and write my own version of minmax_element:

template <class Iter, class R = typename iterator_traits<Iter>::reference>
std::pair<R,R> deref_minmax(Iter first, Iter last)
{
    auto iters = std::minmax_element(first, last);
    return std::pair<R,R>{*iters.first, *iters.second};
}

Which is then just:

int lo, hi;
std::tie(lo, hi) = deref_minmax(lhs.begin(), lhs.end());

This would limit you to just a single copy of the elements (which isn't that much of a big deal with ints), also let you maintain access to the references into the actual container.


In C++17, for fun, we could write a generalized dereferencer:

template <class Tuple>
auto deref(Tuple&& tup) {
    return std::apply([](auto... args) {
        return std::tuple <decltype(*args)...>(*args...);
    }, tup);
}

auto& [lo, hi] = deref(std::minmax_element(lhs.begin(), lhs.end()));

Here lo and hi are references into the container itself.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Your `deref` function can result in undefined behaviour. If the iterators (or whatever `args` ends up being) return prvalues, you get dangling references, since `std::forward_as_tuple` returns an rvalue reference for them. – Xeo Oct 27 '16 at 23:03
  • @Xeo That's up to the caller to not keep references then. – Barry Oct 28 '16 at 00:13
  • 2
    Wrong. You get dangling references at the `return std::forward_as_tuple(...);` point, since the temporaries are created in that scope and are dead as soon as the function returns. – Xeo Oct 28 '16 at 06:59
  • @Xeo Ah, misunderstood your comment initially. Should be fixed now. – Barry Oct 28 '16 at 12:11
2

There's no way of assigning two references at once in the current revision of the standard, if that's what you are after. Note that none of the other answers do that, except Barry's which requires C++17 and a helper template.

However, if you want a read-write access to your minimal and maximal elements, why not just go with the iterators the minmax_element provides you directly? It's likely to generate identical machine code as using references anyway, at least if your lhs is a ContiguousContainer but maybe in other cases as well.

You will need to rely a bit less on the automatic type deduction, for example,

decltype(lhs.begin()) lhsMinIt, lhsMaxIt;
std::tie(lhsMinIt, lhsMaxIt) = std::minmax_element(lhs.begin(), lhs.end());
/* now access your minimum and maximum as *lhsMinIt and *lhsMaxIt */

If you know the type of lhs will be one of the standard containers, you can use a bit cleaner type designation decltype(lhs)::iterator.

The Vee
  • 11,420
  • 5
  • 27
  • 60
2

In C++14 or greater

template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto&&f){
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}
template<class F>
auto fmap_over_tuple( F&& f ) {
  return [f=std::forward<F>(f)](auto&& tuple) {
    using Tuple = decltype(tuple);
    using Tuple_d = std::decay_t<Tuple>;
    auto index = indexer< std::tuple_size< Tuple_d >::value >();
    return index(
      [&f, &tuple](auto&&...Is) {
        using std::get;
        return std::make_tuple(
          f( get<Is>( std::forward<Tuple>(tuple) ) )...
        );
      }
    );
  };
}

so fmap_over_tuple takes a function object. It returns a function object that, when passed a tuple-like, proceeds to call the function object on each element of the tuple-like, and generate a tuple from it.

We then write dereference tuple:

auto dereference_tuple = fmap_over_tuple(
  [](auto&& e) { return *e; }
);

Now in C++17 we do:

auto[Min, Max] = dereference_tuple( std::minmax_element(lhs.begin(), lhs.end() );

and bob is your uncle.

In C++11, just do what you did. Clean enough.

C++14 live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524