Со всеми новыми функциями C ++ (я думаю, C ++ 11 достаточно), что мешает иметь функцию std::minmax, которая возвращает пару ссылок.

Таким образом, если вы загрузите две изменяемые ссылки, они могут быть изменены. Это открытие банки с червями?

#include<functional>
// maybe all this options can be simplified
template<class T1, class T2> struct common;
template<class T> struct common<T, T>{using type = T;};
template<class T> struct common<T const&, T&>{using type = T const&;};
template<class T> struct common<T&, T const&>{using type = T const&;};
template<class T> struct common<T, T&>{using type = T const&;};
template<class T> struct common<T&, T>{using type = T const&;};
template<class T> struct common<T const&, T>{using type = T const&;};
template<class T> struct common<T, T const&>{using type = T const&;};

template<class T1, class T2, class Compare = std::less<>, class Ret = typename common<T1, T2>::type> 
std::pair<Ret, Ret> minmax(T1&& a, T2&& b, Compare comp = {}){
    return comp(b, a) ? 
        std::pair<Ret, Ret>(std::forward<T2>(b), std::forward<T1>(a))
        : std::pair<Ret, Ret>(std::forward<T1>(a), std::forward<T2>(b));
}

Контрольная работа:

#include<cassert>
int main(){
    {
    int a = 1;
    int b = 10;
    auto& small = minmax(a, b).first;
    assert(small == 1);
    small += 1;
    assert(a == 2);
    }{
    int const a = 1;
    int b = 10;
    auto& small = minmax(a, b).first;
    assert(small == 1);
//    small += 1; error small is const reference, because a was const
    }{
    int a = 1;
    int const b = 10;
    auto& small = minmax(a, b).first;
    assert(small == 1);
//    small += 1; error small is const reference, because a was const
    }{
    int const a = 1;
    int const b = 10;
    auto& small = minmax(a, b).first;
    assert(small == 1);
//    small += 1; error small is const reference, because a was const
    }{
    int b = 10;
    auto& small = minmax(int(1), b).first;
    assert(small == 1);
//   small += 1; error small is const reference, because first argument was const
    }{
    int a = 1;
    auto& small = minmax(a, int(10)).first;
    assert(small == 1);
//   small += 1; error small is const reference, because second argument was const
    }
    {
    int const a = 1;
    auto& small = minmax(a, int(10)).first;
    assert(small == 1);
//    small += 1; error small is const reference, because both arguments are const
    }
    {
//    auto& small = minmax(int(1), int(10)).first; // error, not clear why
    auto const& small = minmax(int(1), int(10)).first; // ok
//    auto small2 = minmax(int(1), int(10)).first; // also ok
    assert(small == 1);
//    small += 1; error small is const reference, because both arguments are const
    }
}
3
alfC 13 Май 2018 в 09:53

1 ответ

Лучший ответ

Давным-давно был документ, подобный этому, Говардом Хиннантом: N2199. Самый начальный пример демонстрирует конкретную проблему, которую вы пытаетесь решить:

Функцию нельзя использовать в левой части присваивания:

int x = 1;
int y = 2;
std::min(x, y) = 3;  // x == 3 desired, currently compile time error

Далее в качестве примеров перечисляются часто встречающиеся проблемы с ссылками, смешанные типы и их полезность с типами, предназначенными только для перемещения, а также предлагаются новые версии min и max, которые решают все эти проблемы. - он включает очень тщательную реализацию внизу (которая слишком длинная, чтобы вставлять здесь). Реализация minmax() на основе этого должна быть довольно простой:

template <class T, class U,
    class R = typename min_max_return<T&&, U&&>::type>
inline
std::pair<R, R>    
minmax(T&& a, U&& b)
{
    if (b < a)
        return {std::forward<U>(b), std::forward<T>(a)};
    return {std::forward<T>(a), std::forward<U>(b)};
}

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

Возможность вернуть изменяемые ссылки - это хорошо, но возможность избежать висячих ссылок еще лучше. Анонимно цитирую пример, который я недавно видел:

template<typename T> T sign(T); 

template <typename T> 
inline auto frob(T x, T y) -> decltype(std::max(sign(x - y), T(0))) { 
    return std::max(sign(x - y), T(0)); 
} 

Эта функция имеет неопределенное поведение для всех входов (самый узкий из возможных контрактов?).

Обратите внимание, что в вашей реализации common есть эта проблема. Эти случаи:

template<class T> struct common<T, T&>{using type = T const&;};
template<class T> struct common<T&, T>{using type = T const&;};
template<class T> struct common<T const&, T>{using type = T const&;};
template<class T> struct common<T, T const&>{using type = T const&;};

Все болтаются. Что это значит, если у меня есть:

int i = 4;
auto result = your_minmax(i, 5);

result вот pair<int const&, int const&>, один из которых является ссылкой на i, а другой болтается. Во всех этих случаях нужно делать using type = T;, чтобы быть в безопасности.

1
Barry 16 Авг 2018 в 13:53