9

In C++, if a function returns a std::pair<int, int>, we can auto-receive it as follows:

auto pr = some_function();
std::cout << pr.first << ' ' << pr.second;

Now, C++17 standard provides a beautiful way of unpacking this pair directly into separate variables as follows:

auto [x, y] = some_function();
std::cout << x << ' ' << y;

And then there is std::minmax_element() library function, which returns a pair of iterators. So if I pass a vector<int> to this function, it gives me back pair of iterators pointing to the smallest and the largest element in the vector.

Now one way I can accept these iterators is as usual, and then de-reference them later as follows.

std::vector<int> v = {4,1,3,2,5};
auto [x, y] = std::minmax_element(v.begin(), v.end());
std::cout << (*x) << ' ' << (*y);   // notice the asterisk(*)

Now my question is: Is there any way to de-reference them while unpacking? Or more precisely, given the following code, can I replace var1 and var2 with something that is valid C++ and prints the value pointed by those iterators?

std::vector<int> v = {4,1,3,2,5};
auto [var1, var2] = std::minmax_element(v.begin(), v.end());
std::cout << var1 << ' ' << var2;   // notice there is NO asterisk(*)
MrProgrammer
  • 443
  • 3
  • 13
  • 1
    Here are some suggestion how to write a generic mapper for tuples: https://codereview.stackexchange.com/questions/193420/apply-a-function-to-each-element-of-a-tuple-map-a-tuple. Using that you could `auto pr = ...; auto [var1,var2] = mapper(dereference, pr);` with dereference being like `[](auto iter){return *iter;}`. – Werner Henze May 07 '20 at 15:37
  • As Barry pointed out, this might not be the best idea, since if you call `minmax_element` with an empty range, it will hand you back a pair of non-dereferencable iterators. – Marshall Clow May 07 '20 at 16:11
  • Thank you for the suggestion @MarshallClow but I am not trying to show the best practices here. This is just a minimal example that assumes the range to be non-empty and particularly a `vector`. This is not production code. But hey, thanks for the tip! – MrProgrammer May 07 '20 at 16:15

1 Answers1

11

Sure. Write a function that takes a pair of dereferencable things and returns the result of dereferencing them:

template <typename Iterator,
    typename R = typename std::iterator_traits<Iterator>::reference>
auto deref(std::pair<Iterator, Iterator> p)
    -> std::pair<R, R>
{
    return {*p.first, *p.second};
}

And then use that function:

auto [var1, var2] = deref(std::minmax_element(v.begin(), v.end()));

Noting that this is UB if the range is empty because you're dereferencing the end iterator, twice.


Or, to be nicer:

struct deref_t {
    template <typename It,
        typename R = typename std::iterator_traits<Iterator>::reference>
    friend auto operator|(std::pair<It, It> p, deref_t)
        -> std::pair<R, R>
    {
        return { *p.first, *p.second };
    }
};
inline constexpr deref_t deref{};

Which allows:

auto [var1, var2] = std::minmax_element(v.begin(), v.end()) | deref;
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    You can reduce the deref function considerably since C++14 adds return type deduction: `template auto deref_pair(T const & pair) { return std::make_pair(*pair.first, *pair.second); }`. This also removes the constraint that both types in the pair are the same (though in this case they are the same). – cdhowie May 07 '20 at 15:35
  • 4
    @cdhowie That does something different - if `v` were a `vector`, for instance, your implementation gives a `pair` whereas mine intentionally gives a `pair`. – Barry May 07 '20 at 15:48
  • This is quite nice! Sooo... there's nothing built-in, I guess. – MrProgrammer May 07 '20 at 16:21