Использование max <int> в качестве предикатных разрывов в С++ 11
В С++ 03 следующий код работает нормально:
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::vector<int> v2;
v2.push_back(2);
v2.push_back(3);
v2.push_back(4);
std::transform(v.begin(), v.end(), v2.begin(), v2.begin(), std::max<int>);
return 0;
}
В С++ 11 это не работает, потому что добавлена перегрузка для std::max
, которая содержит initializer_list
. Поэтому вам нужно использовать очень уродливый бросок, чтобы выбрать правильную перегрузку:
static_cast<const int& (*)(const int&, const int&)>(std::max)
У меня есть несколько вопросов.
- Почему стандартный комитет решил сделать это, зная, что это (возможно) нарушит существующий код и заставит пользователя создать уродливый листинг?
- Являются ли будущие стандарты С++ попытками облегчить эту проблему?
- Что такое обходной путь?
Ответы
Ответ 1
Если вы делаете это достаточно часто, вы можете написать прозрачную оболочку-функтор:
struct my_max {
template<class T>
const T& operator()(const T& a, const T& b) const{
return std::max(a, b);
}
};
Тогда вы можете просто сделать
std::transform(v.begin(), v.end(), v2.begin(), v2.begin(), my_max());
всякий раз, когда вам это нужно, вместо того, чтобы писать лямбду или литой каждый раз. Это в основном та же идея, что и прозрачные операторные функторы - пусть аргументы шаблона выводятся на фактическом сайте вызова, а не явно указаны при создании функтора.
Если вы хотите сделать этот любитель, вы можете даже operator()
взять гетерогенные типы и добавить отличную переадресацию и использовать возвращающие возвращаемые типы:
struct my_max {
template<class T, class U>
constexpr auto operator()( T&& t, U&& u ) const
-> decltype(t < u ? std::forward<U>(u) : std::forward<T>(t)){
return t < u ? std::forward<U>(u) : std::forward<T>(t);
}
};
В С++ 14 это упрощено до
struct my_max {
template<class T, class U>
constexpr decltype(auto) operator()( T&& t, U&& u ) const{
return t < u ? std::forward<U>(u) : std::forward<T>(t);
}
};
Ответ 2
Хотя я принял ответ TC, который содержит исчерпывающую разбивку, как указано в комментарии, я хочу подражать прозрачному функтору-компаратору для шаблонов классов, таких как std::less
. Этот ответ предоставляется для критики другими, если у вас что-то не так с синтаксисом.
template <typename T = void>
struct my_max;
template <>
struct my_max<void> {
template<class T, class U>
constexpr decltype(auto) operator()( T&& t, U&& u ) const {
return t < u ? std::forward<U>(u) : std::forward<T>(t);
}
};
Ответ 3
Что такое обходной путь?
Лямбда, вероятно, наиболее читаема и полезна для предикатов и компараторов:
std::transform(v.begin(), v.end(), v2.begin(), v2.begin(),
[] (int a, int b) {return std::max(a,b);} );
Возможно, вы захотите проверить функтор T.C.s, если вам это нужно чаще. Или, с С++ 14:
auto max = [] (auto&& a, auto&& b) -> decltype(auto)
{return a > b? std::forward<decltype(a)>(a) : std::forward<decltype(b)>(b);};
Почему стандартный комитет решил сделать это, зная, что это будет (возможно) нарушить существующий код и заставить пользователя создавать уродливые отливать?
Единственное объяснение заключается в том, что они обнаружили, что новая перегрузка приносит достаточную радость, чтобы компенсировать нарушение существующего кода и необходимость обходных решений в будущем.
Вы можете просто использовать std::max_element
вместо этой новой перегрузки, поэтому вы торгуете синтаксическим сахаром для передачи std::max
-специализации в качестве предикатов для синтаксического сахара для нахождения максимального элемента внутри пары переменных без явного создания массива для него.
В принципе
std::transform( ..., std::max<int> );
// <=>
std::transform( ..., [] (int a, int b) {return std::max(a,b);} );
против
int arr[] {a,b,c,d}; // You don't have an array with a,b,c,d included consecutively yet
int maximum = *std::max_element( std::begin(arr), std::end(arr) ); // ensure arr non-empty!
// <=>
auto maximum = std::max({a, b, c, d});
Может, это компенсирует? С другой стороны, вам едва ли понадобится последняя.
Являются ли будущие стандарты С++ попытками смягчить этот проблема?
Я так не думаю. По-видимому, стандартный комитет действительно не любит удалять недавно введенные функции. Я тоже не вижу такой проблемы; Лямбда выполняет эту работу.