用现代算法的形式写出 `for(x : y) if(p(x)) return x;`。

3
我总是尽可能使用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这样的东西是不存在的——据我所知,无法从被调用函数中调用return到调用者函数。虽然return 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());
}

2
auto it = std::find_if(begin(container), end(container), predicate); return it != end(container) ? *it : throw /*...*/; - T.C.
@T.C.,@Cyber:我考虑过使用std :: find_if,但it != end(container)的检查是不必要的,我想避免这种情况。 - Vittorio Romeo
因为比较两个迭代器太过昂贵? - T.C.
5
你需要找到一个元素,这个过程需要进行 O(N) 次比较。你在意的是,在所有比较都失败的情况下,你会多做一次比较吗? - luk32
3
坚持使用循环,听我的话。只是因为它们被认为是“现代”的算法而把它们强加到代码上,在不需要的情况下给你的代码带来了损害(据我所知,SGI STL从1994年就已经存在了)。请不要这样做。 - jrok
显示剩余8条评论
4个回答

4
使用boost::optional的基于范围的算法。留下reference_type_t作为练习(提示:首先编写iterator_type_t,基于范围上的begin进行adl查找)。
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*替换此类optional
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是相同的迭代器或兼容的终止符。


2

我决定在这种特定情况下使用循环是最具表现力和通常最好的解决方案。


0

我不确定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;
}

对我来说,更自然的做法是检查异常情况(标志未找到)并抛出异常,否则就按照正常的退出路径返回找到的项,而不是在循环中从“正常”条件内部返回,否则就会抛出异常。


0

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接