【问题标题】:Write `for(x : y) if(p(x)) return x;` in modern algorithmic form用现代算法形式写出`for(x : y) if(p(x)) return x;`
【发布时间】:2014-11-20 09:11:00
【问题描述】:

我总是尽可能地尝试使用类似 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

理想情况下,我想将真正的代码 sn-p 编写为:

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(...) 可以工作,但是如果不创建抛出异常的特定算法,我就无法抛出异常。

你有什么建议?应该保持代码原样还是有任何类似算法的方式我可以重写它?

编辑:

正如 cmets 中提到的,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());
}

【问题讨论】:

  • 对于std::find_if &lt;algorithm&gt; 来说听起来不错
  • auto it = std::find_if(begin(container), end(container), predicate); return it != end(container) ? *it : throw /*...*/;
  • @T.C., @Cyber​​:我考虑过使用std::find_if,但检查it != end(container) 是不必要的,我想避免这种情况
  • @VittorioRomeo 您有O(N) 比较来查找元素,并且您关心(仅)在所有这些都失败的情况下,您会得到一个额外的吗?
  • 坚持循环,大声喊叫。您通过在不需要它们的地方强制使用算法来破坏您的代码,因为它们被认为是“现代的”(SGI STL 从 1994 年就已经存在,IIANM)。

标签: c++ algorithm lambda return c++14


【解决方案1】:

使用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 在我上次检查时不支持它们。

您也可以将这样的optionals 替换为简单的T*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&lt;bool&gt;),您最终会得到对上述临时值的引用。

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是一个相同的迭代器或一个兼容的前哨。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      我不确定 makeRangeCastRef() 和您的其他一些代码到底在做什么,但我个人认为 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;
      }
      

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

      【讨论】:

        【解决方案4】:

        使用 Alexandrescu 的 Expected&lt;T&gt;,您可以编写一个算法,该算法返回一个可转换为您正在查找的元素的对象,或者如果未找到则抛​​出异常。类似的东西(没有编译这个):

        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;
        }
        

        【讨论】:

          猜你喜欢
          • 2012-06-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多