当前位置: 首页 > 知识库问答 >
问题:

如何扁平化嵌套的STD::可选?

颛孙航
2023-03-14

注意:这个问题被简单地标记为这个问题的重复,但它不是完全的重复,因为我是专门询问std::optionals的。如果你关心一般情况,还是一个很好的问题。

假设我有嵌套的选项,如下所示(愚蠢的玩具示例):

struct Person{
    const std::string first_name;
    const std::optional<std::string> middle_name;
    const std::string last_name;
};
struct Form{
    std::optional<Person> person;
};

还有这个垃圾邮件功能:

void PrintMiddleName(const std::optional<Form> form){
    if (form.has_value() && form->person.has_value() && form->person->middle_name.has_value()) {
        std::cout << *(*(*form).person).middle_name << std::endl; 
    } else {
        std::cout << "<none>"  << std::endl; 
    }
}

什么是压扁这张可选支票的最佳方法?我做了这样的东西,它不是可变的,但我不太关心这个(如果真的需要,我可以再添加一个级别(用membr3),而且超出这个级别的所有东西都是可怕的代码)。

template<typename T, typename M>
auto flatten_opt(const std::optional<T> opt, M membr){
    if (opt.has_value() && (opt.value().*membr).has_value()){
        return std::optional{*((*opt).*membr)};
    }
    return decltype(std::optional{*((*opt).*membr)}){};
}

template<typename T, typename M1, typename M2>
auto ret_val_helper(){
    // better code would use declval here since T might not be 
    // default constructible.
    T t;
    M1 m1;
    M2 m2;
    return ((t.*m1).value().*m2).value();
}

template<typename T, typename M1, typename M2>
std::optional<decltype(ret_val_helper<T, M1, M2>())> flatten_opt(const std::optional<T> opt, M1 membr1, M2 membr2){
    if (opt.has_value() && (opt.value().*membr1).has_value()){
        const auto& deref1 = *((*opt).*membr1);
        if ((deref1.*membr2).has_value()) {
            return std::optional{*(deref1.*membr2)};
        }
    }
    return {};
}

void PrintMiddleName2(const std::optional<Form> form){
    auto flat  = flatten_opt(form, &Form::person, &Person::middle_name);
    if (flat) {
        std::cout << *flat;
    }
    else {
        std::cout << "<none>"  << std::endl; 
    }
}

神锚

备注:

  • 我不想从std::optional切换到更好的可选。
  • 我不太关心性能,除非返回一个必须复制指针(除非arg是临时的),因为std::optional不支持引用。
  • 我不关心flatten_has_value函数(尽管它很有用),因为如果有一种方法可以很好地扁平化嵌套的选项,那么也有一种方法可以编写该函数。
  • 我知道我的代码看起来很管用,但相当难看,所以我想知道是否有更好的解决方案。

共有2个答案

谭晓博
2023-03-14

对于一个本质上是可链接操作的东西,您已经有了很多helper函数。C++也有用于链的东西:运算符。因此,我可能(ab)使用operator*

对于你的具体情况,你所需要的只是

template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(
        const std::optional<class_t>& opt, 
        const std::optional<member_t> class_t::*member) 
{
    if (opt.has_value()) return opt.value().*member;
    else return {};
}

void PrintMiddleName2(const std::optional<Form> form){
    auto middle = form * &Form::person * &Person::middle_name;
    if (middle) {
        std::cout << *middle;
    }
    else {
        std::cout << "<none>"  << std::endl; 
    }
}

但实际上,您可能还需要用于非可选成员、getter方法和任意转换的变体,我在这里列出了这些变体,尽管我不能百分之百确定它们都能正确编译。

//data member
template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(const std::optional<class_t>& opt, const std::optional<member_t> class_t::*member) {
    if (opt.has_value()) return opt.value().*member;
    else return {};
}
template<class class_t, class member_t>
std::optional<std::remove_cv_t<member_t>> operator*(const std::optional<class_t>& opt, const member_t class_t::*member) {
    if (opt.has_value()) return {opt.value().*member};
    else return {};
}

//member function
template<class class_t, class return_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, std::optional<return_t>(class_t::*member)()) {
    if (opt.has_value()) return opt.value().*member();
    else return {};
}
template<class class_t, class return_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, return_t(class_t::*member)()) {
    if (opt.has_value()) return {opt.value().*member()};
    else return {};
}

//arbitrary function
template<class class_t, class return_t, class arg_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, std::optional<return_t>(*transform)(arg_t&&)) {
    if (opt.has_value()) return transform(opt.value());
    else return {};
}
template<class class_t, class return_t, class arg_t>
std::optional<std::remove_cv_t<return_t>> operator*(const std::optional<class_t>& opt, return_t(*transform)(arg_t&&)) {
    if (opt.has_value()) return {transform(opt.value())};
    else return {};
}

http://coliru.stacked-crooked.com/A/26AA7A62F38BBD89

秦涵涤
2023-03-14

您要查找的操作称为monadic bind操作,有时拼写为和_then(如P0798和Rust中所示)。

您正在使用一个可选 和一个函数t->可选并希望取回一个可选。在这种情况下,函数是指向数据成员的指针,但在这种意义上,它实际上是一个函数。&form::person采用表单并返回可选的

你应该以一种不可知函数的方式来写它。它是一个专门指向成员数据的指针,这一点在这里并不重要,也许明天您会想要一个指向成员函数的指针,甚至是一个自由函数。那就是:

template <typename T,
          typename F,
          typename R = std::remove_cvref_t<std::invoke_result_t<F, T>>,
          typename U = mp_first<R>>
    requires SpecializationOf<R, std::optional>
constexpr auto and_then(optional<T> o, F f) -> optional<U>
{
    if (o) {
        return std::invoke(f, *o);
    } else {
        return std::nullopt;
    }
}

这是用C++编写的许多函数声明之一,即使是用概念也是如此。我将把它作为一个练习来适当地在其中添加引用。我选择将其具体写成->optional而不是->r,因为我认为对于可读性很重要的一点是,您可以看到它实际上返回了某种optional

现在,问题是我们如何将它链接到多个函数。Haskell使用>>=进行一元绑定,但在C++中,它有错误的关联(O>>=f>>=G将首先计算f>>=G并需要括号)。所以下一个最接近的运算符是>>(这在Haskell中意味着一些不同的东西,但我们不是Haskell,所以没关系)。也可以借用Ranges所使用的模型来实现。

所以我们要么在句法上结束:

auto flat  = form >> &Form::person >> &Person::middle_name;

auto flat = form | and_then(&Form::person)
                 | and_then(&Person::middle_name);

将多个单子绑定组合在一起的不同方法是Haskell拼写>=>的操作,称为Kleisli组合。在这种情况下,它接受一个函数T->可选和一个函数U->可选 并生成一个函数T->可选 。这是一个非常讨厌为其编写约束的东西,所以我将跳过它,它看起来像这样(使用Haskell运算符拼写):

template <typename F, typename G>
constexpr auto operator>=>(F f, G g) {
    return [=]<typename T>(T t){
        using R1 = std::remove_cvref_t<std::invoke_result_t<F, T>>;
        static_assert(SpecializationOf<R1, std::optional>);
        using R2 = std:remove_cvref_t<std::invoke_result_t<G, mp_first<R1>>>;
        static_assert(SpecializationOf<R2, std::optional>);

        if (auto o = std::invoke(f, t)) {
            return std::invoke(g, *o);
        } else {
            // can't return nullopt here, have to specify the type
            return R2();
        }
    };
}

然后您可以编写(或者,如果>=>是您可以使用的运算符,至少您可以编写):

auto flat  = form | and_then(&Form::person >=> &Person::middle_name);

因为>=>的结果现在是一个采用形式并返回可选 的函数。

 类似资料:
  • 问题内容: 此问题特定于从[GitHub Repo使用:flatten](https://github.com/amirziai/flatten) 该软件包位于pypi flatten-json 0.1.7上,可以与 此问题特定于软件包的以下组件: 使用递归展平嵌套 用Python递归思考 在Python中展平JSON对象 嵌套如何?: 已用于解压缩最终超过100000列的文件 展平的JSON是否

  • 如我们所知,要使用方法对数组进行展平 那么如何将这个数组平坦化为呢?

  • 本文向大家介绍Python中的扁平化嵌套列表迭代器,包括了Python中的扁平化嵌套列表迭代器的使用技巧和注意事项,需要的朋友参考一下 假设我们有一个嵌套的整数列表;我们必须实现一个迭代器以使其扁平化。每个元素可以是整数,也可以是列表。该列表的元素也可以是整数或其他列表。因此,如果输入类似于[[1,1],2,[1,1]],则输出将为[1,1,2,1,1,1] 为了解决这个问题,我们将遵循以下步骤-

  • 我有一个数据帧字段,它是一个<code>Seq[Seq[String]. 我的用例是字符串,但显然,这可能是通用的。您可以在数据帧转换链中使用此函数,如: 是否有一个Spark内置函数可以做同样的事情?我找不到一个。

  • 给定对象的复杂嵌套集合,如: 是否存在一个泛型方法来将其平坦化并获得包含在其中的所有的单个? null