写入`for(x:y)if(p(x))return x;`以现代算法形式

Write `for(x : y) if(p(x)) return x;` in modern algorithmic form

本文关键字:算法 if for 写入 return      更新时间:2023-10-16

我总是尽可能使用类似STL的算法,因为它们简洁且极具表现力。

我在我的一个库中有这样的代码:

auto& findFlag(const std::string& mName)
{
    for(auto& f : makeRangeCastRef<Flag>(getFlags())) 
        if(f.hasName(mName)) 
             return f;
    throw Exception::createFlagNotFound(mName, getNamesStr());
}

我想用现代C++算法的形式写它,但我不知道如何处理早期的return和可能的throw

for(auto& value : container) if(predicate(value)) return value; 
//                                                ^~~~~~
// IMPORTANT: return from the caller function, not the algorithm itself

理想情况下,我想把真实的代码片段写为:

auto& findFlag(const std::string& mName)
{
    early_return_if(makeRangeCastRef<Flag>(getFlags()), 
        [&mName](const auto& f){ return f.hasName(mName); });
    throw Exception::createFlagNotFound(mName, getNamesStr());
}

显然,像early_return_if这样的东西是不可能存在的——据我所知,没有办法从被调用者在调用者函数上调用returnreturn early_return_if(...)可以工作,但如果不创建一个抛出异常的特定算法,我就无法抛出异常。

你有什么建议?应该保持代码原样吗?或者有没有类似算法的方法可以重写它

编辑:

正如评论中所提到的,std::find_if是一个很好的候选者,但有一个不必要的检查是可以避免的:

auto& findFlag(const std::string& mName)
{
    auto container(makeRangeCastRef<Flag>(getFlags())); // Need to write this out...
    // Need to keep track of the returned iterator...
    auto it(findIf(container, [&mName](const auto& f){ return f.hasName(mName); }));
    if(it != container.end()) return *it; // I don't like this either...
    throw Exception::createFlagNotFound(mName, getNamesStr());
}

一种使用boost::optional的基于范围的算法。reference_type_t作为练习离开(提示:首先根据范围上begin的adl查找编写iterator_type_t)。

template<class Range, class Function>
boost::optional< reference_type_t<Range> >
search_if( Range&& r, Function&& f ) {
  for( auto&& x:std::forward<Range>(r) ) {
    if (f(x))
      return std::forward<decltype(x)>(x);
  }
  return {};
}

然后:

auto& findFlag(const std::string& mName) {
  auto result = search_if(
    makeRangeCastRef<Flag>(getFlags()),
    [&](auto&& f){return f.hasName(mName); }
  );
  if (result) return *result;
  throw Exception::createFlagNotFound(mName, getNamesStr());
}

您可以完全取消异常,并让findFlag本身返回一个optional(基本上就是search_if)。

不,您不能将流控制注入到调用您的函数中。

以上内容确实依赖于支持可选引用的optional。这些都是有争议的:即将推出的std::optional上次我检查时并不支持它们。

您也可以用简单的T* s替换此类optional s。

template<class Range, class Function>
value_type_t<Range>*
search_if( Range&& r, Function&& f ) {
  for( auto&& x:std::forward<Range>(r) ) {
    if (f(x))
      return &x;
  }
  return nullptr;
}

但缺点是,如果你的范围很奇怪(比如std::vector<bool>),你最终会引用上面的临时值。

value_type_treference_type_t的示意图,它们采用范围/容器并输出该范围/容器值/参考类型:

namespace adl_aux {
  using std::begin;
  template<class R> using iterator_t = decltype( begin(std::declval<R>()) );
}
using adl_aux iterator_t;
template<class T>struct void{using type=void;}
template<class T>using void_t=typename void<T>::type;
template<class R,class=void>
struct value_type {};
template<class R>
struct value_type<R, void_t< iterator_t<R> > {
  using type = std::iterator_traits< iterator_t<R> >::value_type;
};
template<class R>using value_type_t = typename value_type<R>::type;
template<class R,class=void>
struct reference_type {};
template<class R>
struct reference_type<R, void_t< iterator_t<R> > {
  using type = std::iterator_traits< iterator_t<R> >::reference_type;
};
template<class R>using reference_type_t = typename reference_type<R>::type;

它可以变得更健壮——迭代器上的SFINAE检查可以检查begin的返回类型上的迭代器公理,并确保end是一个相同迭代器或兼容的sentinal。

我认为,对于这种特殊情况,使用循环是最具表现力的、通常也是最好的解决方案。

我不确定RangeCastRef<>究竟是什么()和你的一些其他代码正在做,但我个人认为,如果你写得更像这样,find_if版本比你的原始版本更可读:

auto& findFlag(const std::string& mName)
{
    auto findIt = find_if(cbegin(getFlags()), cend(getFlags()), [&](const auto& f){ return f.hasName(mName); });
    if (findIt == container.end()) throw Exception::createFlagNotFound(mName, getNamesStr());
    return *findIt;
}

对我来说,检查异常条件(未找到标志)并抛出异常似乎更自然,否则会返回找到的项的正常退出路径,而不是在基于循环的版本中,在"正常"条件下从循环内部返回,否则会抛出异常。

使用Alexandrescu的Expected<T>,您可以编写一个算法,返回可转换为您要查找的元素的对象,或者在找不到时抛出异常。类似(没有编译这个):

template <class It, class Pred, class Else>
Expexted<T&> find_if_ref(It first, It last, Pred pred, Else el)
{
    auto it = find_if(first, last, pred);
    if (it == last) {
        try {
            el();
        }
        catch (...) {
            return std::current_exception();
        }
    }
    return *it;
}