C++ 范围像 Java 流一样查找并返回 std::optional

Posted

技术标签:

【中文标题】C++ 范围像 Java 流一样查找并返回 std::optional【英文标题】:C++ ranges find and return std::optional like Java stream 【发布时间】:2021-03-30 17:10:00 【问题描述】:

在 Java 中,Stream.findFirst() returns Optional<T>。我希望std::ranges::find() 有类似的行为。如果未找到该值,则返回 last 迭代器。如果T 是一个结构并且我试图从中取出一个成员,这很不方便。下面是一段演示代码:

    struct Person 
        int age;
        int height;
    ;

    std::vector<Person> people;
    people.emplace_back(20, 100);
    people.emplace_back(23, 170);

    // find the height of a 23yo person
    std::optional<int> height1 = std::ranges::find(people, 23, &Person::age)->height;

    // find the height of a 26yo person
    std::optional<int> height2 = std::ranges::find(people, 26, &Person::age)->height;  // error

当然,我可以在每个 find 周围放置一些包装器代码来转换迭代器,但这会使代码变得如此冗长和样板式。我想知道 C++20 中是否有更惯用的方式来做到这一点?

    std::optional<int> height2;
    auto iter = std::ranges::find(people, 26, &Person::age);
    if (iter == people.end()) 
        height2 = std::nullopt;
     else 
        height2 = iter->height;
    

【问题讨论】:

您始终可以编写一个包装函数来封装.end() 的检查。无需到处复制代码。 考虑一下,我实际上想要一个包装器,1) 从 borrowed_iterator&lt;T&gt; 转换为 std::optional&lt;T&gt;,以及 2) 允许投影从 T 中提取成员。您能否在回答中提供此类包装器的模板示例? Anyway, op* and op-&gt; simply assume they are used correctly on pain of UB. 是的,!resultresult == std::end(container) 更方便一点,但这与直接使用迭代器相平衡。此外,可选的迭代器通常比简单的迭代器大... 【参考方案1】:
template <class ...ArgsT>
auto OpRangesFind(auto &&Range, ArgsT &&...Args)

    auto Iter = std::ranges::find(Range, std::forward<ArgsT>(Args)...);
    return Iter != Range.end() ? Iter : std::optional<decltype(Iter)>std::nullopt;


int main()

    /* ... */

    auto OpIter = OpRangesFind(people, 26, &Person::age);
    if (!OpIter.has_value()) 
        std::cout << "not found\n";
     else 
        std::cout << "found\n";
        std::cout << "height: " << OpIter.value()->height << "\n";
        //                               ^^^^^^^^
    

【讨论】:

OpIter 不是 dangling 的可选项,而不是 Person @CrendKing 嗯,是的,有一个错误,我会尝试修复它。 @CrendKing 已编辑,您能再检查一下吗? 谢谢。有用。我对其进行了一些更改以满足我的需要(返回成员变量而不是迭代器)并在答案中提供。感谢您的帮助。【参考方案2】:

这是我自己编写通用包装器的解决方案。与 Sprite 的版本相比,我的版本返回的是成员变量而不是迭代器。

template<std::ranges::input_range R, class T, class ProjFind = std::identity, class ProjOut = std::identity>
requires std::indirect_binary_predicate<std::ranges::equal_to,
                                        std::projected<std::ranges::iterator_t<R>, ProjFind>,
                                        const T *>
auto optionalFind( R&& r, const T& value, ProjFind projFind = , ProjOut projOut =  ) 
    const auto iter = std::ranges::find(r, value, projFind);
    return iter == r.end() ? std::nullopt : std::optional(std::invoke(projOut, *iter));

并与它一起使用

std::optional<int> height2 = optionalFind(people, 26, &Person::age, &Person::height);

std::optional<Person> person = optionalFind(people, 26, &Person::age);

【讨论】:

以上是关于C++ 范围像 Java 流一样查找并返回 std::optional的主要内容,如果未能解决你的问题,请参考以下文章

lambda 是不是像 C++ 中的函数一样内联?

C++:使用find查找Vector中的复杂类型元素

使用std :: equal_range查找字符串向量中出现的前缀范围

❥关于C++之std::ios_base::fmtflags/setf

C++ STL set和multiset的使用

记录一个比较少用的容器C++ std::bitset