写入`for(x:y)if(p(x))return x;`以现代算法形式
Write `for(x : y) if(p(x)) return x;` in modern algorithmic form
我总是尽可能使用类似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());
}
一种使用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_t
和reference_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;
}
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 基于ELO的团队匹配算法
- C++选择排序算法中的逻辑错误
- 我的简单if-else语句是如何无法访问的代码
- 如何将enable-if与模板参数和参数包一起使用
- 无论条件是否为true,if总是在c++中执行
- Arduino:for/while/if在void setup()或void loop()之前?——错误:之前需要不合格
- 有没有办法将谓词中的元素偏移量传递给 std 算法?
- C++A*算法并不总是在路径中具有目标节点
- 排序算法c++
- Insert函数不适用于2 if语句C++
- If语句未被求值C++
- C++嵌套if语句,基本货币交换
- 多个If语句与使用逻辑运算符计算条件的单个语句的比较
- 构建可组合有向图(扫描仪生成器的汤普森构造算法)
- 是否可以使用if constexpr删除控制流语句
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 更好的 if 条件算法
- 写入`for(x:y)if(p(x))return x;`以现代算法形式
- 使用if循环排序时出现意外结果-糟糕的算法