Есть хороший способ назначить std :: minmax (a, b) для std :: tie (a, b)? - PullRequest
55 голосов
/ 24 июня 2019
std::tie(a, b) = std::minmax(a, b);

Я думаю, что это интуитивно понятный код.Чисто и понятно.Жаль, что он не работает как задумано, как std::minmax шаблоны для const&.Следовательно, если значения поменялись местами внутри std::pair<const&, const&>, то одно назначение перезапишет другое значение:

auto[a, b] = std::make_pair(7, 5);

std::tie(a, b) = std::minmax(a, b);

std::cout << "a: " << a << ", b: " << b << '\n';

a: 5, b: 5

Ожидаемоевывод здесь a: 5, b: 7.


Я думаю, что это важно, поскольку реализация функций преобразования для применения функции к некоторым диапазонам требует таких выражений для интуитивно понятных лямбд.Например:

std::vector<int> v{ 0, 1, 0, 2, 0 };
std::vector<int> u{ 1, 0, 1, 0, 1 };

perform(v.begin(), v.end(), u.begin(), [](auto& a, auto& b){ 
    std::tie(a, b) = std::minmax(a, b);    
}); 

//v would be == {0, 0, 0, 0, 0}
//u would be == {1, 1, 1, 2, 1}

Одно решение, которое я нашел, заключалось в явном создании std::tuple без каких-либо ссылочных квалификаторов над std::pair<const&, const&> для принудительного применения копии:

std::tie(a, b) = std::tuple<int, int>(std::minmax(a, b)); 

Но эта <int, int> избыточность кажется довольно ужасной, особенно если сказать auto& a, auto& b раньше.


Есть ли хороший, короткий способ выполнить это задание?Может быть, это неправильное направление, и просто сказать, что if (a >= b) { std::swap(a, b); } будет лучшим подходом здесь?

Ответы [ 3 ]

57 голосов
/ 24 июня 2019

Вы можете использовать список инициализатора для minmax:

std::tie(a, b) = std::minmax({a, b});

Это вызывает создание временных объектов, как при использовании унарный плюс , но имеет то преимущество, что оно работает с типами, в которых также нет оператора унарный плюс .

using namespace std::string_view_literals;

auto [a, b] = std::make_pair("foo"sv, "bar"sv);
std::tie(a, b) = std::minmax({a, b});
std::cout << "a: " << a << ", b: " << b << '\n';

Выход:

a: bar, b: foo

Может ли быть, что это неправильное направление и просто сказать, что if (a >= b) { std::swap(a, b); } будет лучшим подходом здесь?

Я бы сделал это if(b < a) std::swap(a, b); из-за требования Сравните 1 , но да, я подозреваю, что это будет быстрее, и все еще очень ясно, чего вы хотите достичь .


[1] Сравнить [...] Возвращаемое значение операции вызова функции, примененной к объекту. типа, удовлетворяющего Compare, когда контекстно преобразуется в bool, возвращает true, если первый аргумент вызова появляется перед второй в строгом отношении слабого порядка, индуцированного этим типом, и иначе false.

24 голосов
/ 24 июня 2019

Вы можете применить это с определенной степенью краткости следующим образом.

std::tie(a, b) = std::minmax(+a, +b);

std::cout << "a: " << a << ", b: " << b << '\n';

Объяснение: встроенный унарный оператор плюс, для симметрии с унарным минусом, возвращает свой операнд по значению (он также выполняет обычные арифметические преобразования, но это не относится к int с).Это означает, что он должен создать временный объект, даже если этот временный объект - не что иное, как копия операнда.Но для использования minmax в этом примере достаточно: обмен ссылками здесь больше не присваивается, поскольку ссылки в правой части (аргументы const int&, переданные minmax) не относятся кте же объекты, что и на левой стороне (внутри tuple ссылок, созданных std::tie).

Вывод по желанию:

a: 5,б: 7

3 голосов
/ 26 июня 2019

Иногда шаг назад и поиск другого пути окупаются:

if (b < a)
    std::iter_swap(&a, &b);

Это сжато и, как правило, более эффективно, по крайней мере, на уровне. Может быть, упаковать его в свою собственную функцию:

template <class T>
void reorder(T& a, T& b)
noexcept(noexcept(b < a, void(), std::iter_swap(&a, &b))) {
    if (b < a)
        std::iter_swap(&a, &b);
}

Я использую std::iter_swap(), поэтому мне не нужно использовать using std::swap; swap(a, b) двухэтапный для общности в pre-C ++ 2a, который вводит объекты точек настройки делает это устаревшим.

...