[英]Chain Optionals in C++
How to avoid nested if statements with chained optionals in C++?如何避免 C++ 中带有链式选项的嵌套 if 语句?
For example, if type A contains an std::optional<B> b
and type B an std::optional<C> c
, I would like to be able to write something like:例如,如果类型 A 包含一个
std::optional<B> b
并且类型 B 一个std::optional<C> c
,我希望能够编写如下内容:
const auto v = if_exists(if_exists(a->b)->c);
And v would get the value from c or an empty optional if either b or c are empty optionals.如果 b 或 c 是空的可选项,则 v 将从 c 或空的可选项中获取值。
I think this would be nicer that nested ifs like this:我认为像这样嵌套 if 会更好:
if (a->b) {
const auto b = *(a->b);
if (b->c) {
const auto c = *(b->c);
}
}
The following question seems to go in this direction but I am not sure how to adapt it to my use-case: Haskell style "Maybe" type & *chaining* in C++11以下问题似乎朝着这个方向发展,但我不确定如何使其适应我的用例: Haskell style "Maybe" type & *chaining* in C++11
You can do something like this (pseudocode-ish; link to buildable code is provided below):你可以做这样的事情(pseudocode-ish;下面提供了可构建代码的链接):
// wrap std::optional for chaining
template <class T> class Maybe {
std::optional<T> t;
// ... constructors etc
// Maybe chaining
// If A has a member named m of type M,
// then Maybe<A>.fetch(&A::m) returns a Maybe<M>
template <class M>
Maybe<M> fetch(M T::*mem_ptr) {
return (bool(t)) ? Maybe<M>((*t).*mem_ptr) : Maybe<M>() ;
}
// Maybe chaining special case
// If A has a member named m, which is itself a Maybe<M>,
// then return it without wrapping it in an additional Maybe
template <class M>
Maybe<M> fetch(Maybe<M> T::*mem_ptr) {
return (bool(t)) ? ((*t).*mem_ptr) : Maybe<M>() ;
}
};
Now if you have this:现在,如果你有这个:
struct C { int d ; }
struct B { C c; }
struct A { B b; }
A a;
Maybe<A> ma;
and you can do this你可以这样做
int d = a.b.c.d;
you cannot do the same with ma
, but you can use the next best thing, namely:你不能对
ma
做同样的事情,但你可以使用下一个最好的东西,即:
Maybe<int> md = ma.fetch(&A::b).fetch(&B::c).fetch(&C::d);
And you can still use this if you Maybe
-ify any or all struct
members above:如果你
Maybe
-ify 上面的任何或所有struct
成员,你仍然可以使用它:
struct C { Maybe<int> d ; }
struct B { Maybe<C> c; }
struct A { Maybe<B> b; }
Live example (not production quality but it builds) .现场示例(不是生产质量,但可以构建) 。
You might use你可能会使用
template <typename T, typename F>
auto convert_optional(const std::optional<T>& o, F&& f)
-> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *o))>>
{
if (o)
return std::invoke(std::forward<F>(f), *o);
else
return std::nullopt;
}
template <typename T, typename F>
auto convert_optional(std::optional<T>& o, F&& f)
-> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *o))>>
{
if (o)
return std::invoke(std::forward<F>(f), *o);
else
return std::nullopt;
}
template <typename T, typename F>
auto convert_optional(std::optional<T>&& o, F&& f)
-> std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f), *std::move(o)))>>
{
if (o)
return std::invoke(std::forward<F>(f), *std::move(o));
else
return std::nullopt;
}
or或者
template <typename> struct is_optional : std::false_type {};
template <typename T> struct is_optional<std::optional<T>> : std::true_type {};
template <typename O, typename F>
auto convert_optional(O&& o, F&& f)
-> std::enable_if_t<
is_optional<std::decay_t<O>>::value,
std::optional<std::decay_t<decltype(std::invoke(std::forward<F>(f),
*std::forward<O>(o)))>>>
{
if (o)
return std::invoke(std::forward<F>(f), *o);
else
return std::nullopt;
}
and your example becomes:你的例子变成:
auto c = convert_optional(convert_optional(a, &A::b).value_or(std::nullopt),
&B::c).value_or(std::nullopt);
convert_optional(a, &A::b)
will return std::optional<std::optional<B>>
convert_optional(a, &A::b)
将返回std::optional<std::optional<B>>
You might even simplify by additional function:您甚至可以通过附加功能进行简化:
template <typename O, typename F>
auto convert_optional_fact(O&& o, F&& f)
-> decltype(convert_optional(std::forward<O>(o),
std::forward<F>(f)).value_or(std::nullopt))
{
return convert_optional(std::forward<O>(o),
std::forward<F>(f)).value_or(std::nullopt);
}
and then接着
auto c = convert_optional_fact(convert_optional_fact(a, &A::b), &B::c);
This can be achieved with a simple macro.这可以通过一个简单的宏来实现。
#define CHAIN(OPTIONAL, MEMBER) \
([](auto &&opt) { \
return opt ? std::optional{opt->MEMBER} : std::nullopt; \
}(OPTIONAL))
const auto v = CHAIN(CHAIN(a, b), c);
Note that there is a proposal to allow this optional chaining in C++:请注意,有一个提议允许在 C++ 中使用此可选链接:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0798r3.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0798r3.html
I like this part:我喜欢这部分:
This is common in other programming languages.
这在其他编程语言中很常见。 Here is a list of programming languages which have a optional-like type with a monadic interface or some similar syntactic sugar:
下面是一个编程语言列表,它们具有类似于可选类型的单子接口或一些类似的语法糖:
- Java: Optional
Java:可选
- Swift: Optional
斯威夫特:可选
- Haskell: Maybe
哈斯克尔:也许
- Rust: Option
锈:选项
- OCaml: option
OCaml:选项
- Scala: Option
斯卡拉:选项
- Agda: Maybe
阿格达:也许
- Idris: Maybe
伊德里斯:也许
- Kotlin: T?
科特林:T?
- StandardML: option
StandardML:选项
- C#: Nullable
C#:可以为空
Here is a list of programming languages which have a optional-like type without a monadic interface or syntactic sugar:
以下是一个没有单子接口或语法糖的类可选类型的编程语言列表:
C++
C++
I couldn't find any others
我找不到其他人
The proposal would allow for code like that:该提案将允许这样的代码:
std::optional<image> get_cute_cat (const image& img) {
return crop_to_cat(img)
.and_then(add_bow_tie)
.and_then(make_eyes_sparkle)
.transform(make_smaller)
.transform(add_rainbow);
}
Using the code given in referenced question, You can do something like this使用参考问题中给出的代码,您可以执行以下操作
maybe_do(a->b, [](B &b){
return maybe_do(b.c, [](C &c){
//do what You want to do with the C optional
})
});
C++23 introduces and_then or or_else to address this inconvenience. C++23 引入了and_then或or_else来解决这个不便。
Here is some paper with proposal.这是一些带有提案的论文。
Before we can use C++23 you can try write some template which could resolve this.在我们可以使用 C++23 之前,您可以尝试编写一些可以解决此问题的模板。
My attempt:我的尝试:
namespace detail {
template <auto Field, class T>
struct field_from_opt;
template<typename T, typename FieldType, FieldType T::*ptr>
struct field_from_opt<ptr, T>
{
static auto get(const std::optional<T>& x) -> std::optional<FieldType>
{
if (x) return (*x).*ptr;
return {};
}
};
}
template<auto Field, typename T>
auto if_exists(const std::optional<T>& x)
{
return detail::field_from_opt<Field, T>::get(x);
}
https://godbolt.org/z/dscjYqrx1 https://godbolt.org/z/dscjYqrx1
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.