Подтвердить что ты не робот

Есть ли способ легко справиться с функциями, возвращающими std:: pairs?

С++ 11 имеет функцию std::minmax_element, которая возвращает пару значений. Это, однако, довольно запутывает для обработки и чтения и создает дополнительную, более позднюю бесполезную переменную, чтобы загрязнить область.

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

Есть ли лучший способ сделать это? Что-то вроде:

int lhsMin;
int lhsMax;
std::make_pair<int&, int&>(lhsMin, lhsMax).swap(
    std::minmax_element(lhs.begin(), lhs.end()));
4b9b3361

Ответ 1

Это похоже на общий случай, чтобы вызвать вспомогательную функцию:

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 - это С++ 14, но полная реализация может быть выполнена в С++ 11.

Примечание. Я бы сохранил повторяющийся decltype в deref тип возвращаемого значения даже в С++ 14, так что SFINAE может применяться.

Смотрите вживую на Coliru

Ответ 2

Со структурированным связыванием из С++ 1z вы можете напрямую выполнить

auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());

Ответ 3

Чтобы избежать загрязнения вашей области видимости, вы можете заключить задание в меньшую область:

int lhsMin, lhsMax;

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

вы можете использовать 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);
}();

Ответ 4

Я просто буду более прямым и напишу свою собственную версию 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};
}

Что тогда просто:

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

Это ограничило бы вас только одной копией элементов (что не очень важно для int s), а также позволит вам поддерживать доступ к ссылкам в фактический контейнер.


В С++ 17, для удовольствия, мы могли бы написать обобщенный 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()));

Здесь lo и hi являются ссылками в сам контейнер.

Ответ 5

Невозможно назначить сразу две ссылки в текущей версии стандарта, если это то, что вам нужно. Обратите внимание, что ни один из других ответов не делает этого, кроме Barry, для которого требуется С++ 17 и вспомогательный шаблон.

Однако, если вы хотите, чтобы доступ на чтение и запись к вашим минимальным и максимальным элементам, почему бы просто не пойти с итераторами, minmax_element предоставляет вам напрямую? Скорее всего, он будет генерировать идентичный машинный код, как использование ссылок, по крайней мере, если ваш lhs является ContiguousContainer, но может быть и в других случаях.

Вам нужно будет немного полагаться на вычет автоматического типа, например

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 */

Если вы знаете, что тип lhs будет одним из стандартных контейнеров, вы можете использовать обозначение типа бит чистого типа decltype(lhs)::iterator.

Ответ 6

В С++ 14 или выше

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

поэтому fmap_over_tuple принимает объект функции. Он возвращает объект функции, который, когда передается кортеж, переходит к вызову объекта функции для каждого элемента кортежа и генерирует кортеж из него.

Затем мы пишем фрагмент разыменования:

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

Теперь в С++ 17 делаем:

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

и Боб - твой дядя.

В С++ 11 просто сделайте то, что вы сделали. Очистите достаточно.

С++ 14 живой пример.