有没有办法轻松处理返回 std::pairs 的函数?
Is there a way to easily handle functions returning std::pairs?
C++11 具有函数 std::minmax_element
,其中 returns 一对值。然而,这在处理和阅读时非常混乱,并且会产生一个额外的、后来无用的变量来污染范围。
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()));
为避免污染您的作用域,您可以将赋值包含在较小的作用域中:
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);
}();
使用 C++17 中的结构化绑定,您可以直接执行
auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());
这看起来很常见,可以提示辅助函数:
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
是 C++14,但是完整的实现 can be made in C++11.
注意:即使在 C++14 中,我也会在 deref
的 return 类型中保留重复的 decltype
,以便 SFINAE 可以应用。
我会更直接地编写我自己的 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 来说没什么大不了的),还可以让您保持对实际容器的引用的访问。
在 C++17 中,为了好玩,我们可以编写一个通用的解引用器:
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
是对容器本身的引用。
在当前的标准修订版中,无法同时分配两个 references,如果这就是您所追求的。请注意 none 的其他答案都是这样做的,除了 Barry 的答案需要 C++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
。
在 C++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
接受一个函数对象。它 returns 一个函数对象,当传递类似元组时,继续调用类似元组的每个元素的函数对象,并从中生成一个元组。
然后我们写解引用元组:
auto dereference_tuple = fmap_over_tuple(
[](auto&& e) { return *e; }
);
现在在 C++17 中我们这样做:
auto[Min, Max] = dereference_tuple( std::minmax_element(lhs.begin(), lhs.end() );
鲍勃是你叔叔。
在 C++11 中,做你做过的事。够干净了。
C++11 具有函数 std::minmax_element
,其中 returns 一对值。然而,这在处理和阅读时非常混乱,并且会产生一个额外的、后来无用的变量来污染范围。
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()));
为避免污染您的作用域,您可以将赋值包含在较小的作用域中:
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);
}();
使用 C++17 中的结构化绑定,您可以直接执行
auto [lhsMinIt, lhsMaxIt] = std::minmax_element(lhs.begin(), lhs.end());
这看起来很常见,可以提示辅助函数:
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
是 C++14,但是完整的实现 can be made in C++11.
注意:即使在 C++14 中,我也会在 deref
的 return 类型中保留重复的 decltype
,以便 SFINAE 可以应用。
我会更直接地编写我自己的 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 来说没什么大不了的),还可以让您保持对实际容器的引用的访问。
在 C++17 中,为了好玩,我们可以编写一个通用的解引用器:
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
是对容器本身的引用。
在当前的标准修订版中,无法同时分配两个 references,如果这就是您所追求的。请注意 none 的其他答案都是这样做的,除了 Barry 的答案需要 C++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
。
在 C++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
接受一个函数对象。它 returns 一个函数对象,当传递类似元组时,继续调用类似元组的每个元素的函数对象,并从中生成一个元组。
然后我们写解引用元组:
auto dereference_tuple = fmap_over_tuple(
[](auto&& e) { return *e; }
);
现在在 C++17 中我们这样做:
auto[Min, Max] = dereference_tuple( std::minmax_element(lhs.begin(), lhs.end() );
鲍勃是你叔叔。
在 C++11 中,做你做过的事。够干净了。