首页 > 解决方案 > 现代 C++ - 如何使用 std::optional 和引用实现 FirstOrDefault

问题描述

在使用 C# 和 Javascript/Typescript 多年后,我再次选择了 C++。我已经非常习惯编写使用算法来操作和查询容器的代码,例如:

C#

var mocData = Enumerable.Range(0, 100)
  .Where(n => n % 2 == 1)
  .Select(n => new Item(n))
  .ToList();

Javascript(使用 Lodash <3 )

const mocData = _.chain(_.range(0, 100))
  .filter(n => n % 2 == 1)
  .map(n => ({ n: `Item #${n}` }))
  .value();

现在,C++ 是一个非常不同的野兽,我正在尝试做的事情,但没有取得多大成功(从简洁的语法/低冗长的角度来看),是为 STL 的函数编写一个包装器,允许我处理容器内的项目,而不是它们的副本,使用值语义,同时还考虑了“可空性(即 std::optional 包装值)。例如,如果您不关心可空性,则查找函数很容易编写。这就是我的方法想使用它,以及它是如何实现的:

template<typename T, typename Predicate>
auto find(const std::vector<T>& items, Predicate pred) -> T& {
    auto it = std::find_if(items.begin(), items.end(), pred);
    if (it == items.end()) {
        throw std::runtime_error{ "Sequence contains no matching elements." };
    }
    return *it;
}    

std::vector<MyClass> items = { ... };
const auto& item = find(items, [](const MyClass& o) { return o... });
++item.count; // Modifies the item inside of the container

现在,如果我想考虑可空性,如在 C# 或 javascript 中:

C#

var item = items.FirstOrDefault(o => o...);
item?.DoSomething();

Javascript

const item = _.find(items, o => o...);
item?.doSomething();

我必须在 C++ 中使用 std::optional 。并且为了避免复制(以便能够修改容器中的项目!!),我必须返回 std::optional。但是 std::optional 不能直接与 T& 一起使用,所以我必须使用 std::reference_wrapper 作为它的类型:

template<typename T>
using Ref = std::reference_wrapper<T>;

template<typename T, typename Predicate>
auto firstOrDefault(std::vector<T>& vec, Predicate pred) -> std::optional<Ref<T>> {
    auto it = std::find_if(vec.begin(), vec.end(), pred);
    if (it == vec.end()) {
        return std::nullopt;
    }
    return std::ref(*it);
}

现在,丑陋的部分是使用返回值:

const auto item = firstOrDefault(items, predicate...);
if (item.has_value()) {
    item
        .value() // unwrap the ref from std::optional
        .get()   // unwrap T& from std::reference_wrapper<T>
        .do_something() // jeeeez so hard to use it!!
}

我知道我可以使用指针,并且使用非拥有指针作为引用并没有什么不好,也许使用 C++ 核心指南的类型......但是引用是为了维护值语义,我想使用它们即使它们不能很好地使用 STL 中的容器和包装器。

有没有好的方法来实现这一目标?

编辑:我已经修改了标题以更好地表达我不想要一个类似 LINQ 的库,我试图了解现代 C++20 是否可以使用可空性和引用(不是指针)体面的语法。

标签: c++algorithmlinqfunctional-programminglodash

解决方案


推荐阅读