我想要一个优雅的方法来安全地读取一个被 Package 在“可空类型”中的字段中的数据,比如std::optional和std::shared_ptr。
#include <iostream>
#include <memory>
#include <optional>
struct Entry
{
std::optional<std::string> name;
};
struct Container
{
std::optional<std::shared_ptr<Entry>> entry;
};
int main()
{
Entry entry{"name"};
Container container{std::make_shared<Entry>(entry)};
// ...
return 0;
}
要从给定Container的Entry中读取“name”字段,我可以写:
std::cout << *((*container.entry)->name) << std::endl;
但是我觉得这并不容易读或写,而且由于可选的和共享的指针可能没有被设置,我也不能。
我想避免这样的代码:
if (container.entry)
{
const auto ptr = *container.entry;
if (ptr != nullptr)
{
const auto opt = ptr->name;
if (opt)
{
const std::string name = *opt;
std::cout << name << std::endl;
}
}
}
而我在寻找更像这样的东西:
const auto entry = recursive_dereference(container.entry);
const auto name = recursive_dereference(entry.name);
std::cout << name.value_or("empty") << std::endl;
这将基于此recursive_dereference实现。
问题是,如果一个可选的或者shared_ptr没有设置,它就会崩溃。有没有办法修改recursive_dereference
,使它在一个可选的返回结果,当一个字段没有设置时,这个可选的返回结果是空的?
我想我们可以使用std::enable_if_t<std::is_constructible<bool, T>::value
来检查该字段是否可以在if
中作为bool使用(对于可选项和共享指针也是如此),这将允许我们检查它们是否被设置。如果它们被设置,我们可以继续解引用递归。如果没有设置,我们可以中断递归并返回一个最终类型的空可选项。
不幸的是,我无法将其公式化为工作代码。解决方案最多只能限于“C++14 with optionals”。
更新日期:
首先,我意识到使用std::is_constructible<bool, T>
是不必要的。recursive_dereference
检查一个类型是否可以被解除引用,当它可以的时候,我们可以检查它是否用if (value)
设置。至少它可以与可选和共享指针一起工作。
我发现的另一种方法是首先单独检查解引用该值是否安全,然后不修改地调用recursive_dereference
。
所以我们可以做到:
if (is_safe(container.entry)) {
const auto entry = recursive_dereference(container.entry);
// use entry
}
is_safe
的实施:
template<typename T>
bool is_safe(T&& /*t*/, std::false_type /*can_deref*/)
{
return true;
}
// Forward declaration
template<typename T>
bool is_safe(T&& t);
template<typename T>
bool is_safe(T&& t, std::true_type /*can_deref*/)
{
if (t)
{
return is_safe(*std::forward<T>(t));
}
return false;
}
template<typename T>
bool is_safe(T&& t)
{
return is_safe(std::forward<T>(t), can_dereference<T>{});
}
我仍然希望有一个更好的解决方案,可以避免分别检查和引用,这样我们就可以在一次传递中得到一个值或“空”。
更新2
我设法得到了一个不需要单独检查的版本。我们必须显式地给予我们期望的最终类型作为模板参数。它返回一个可选的值,或者一个空的可选的,如果沿着没有设置一个引用的话。
template <typename FT, typename T>
auto deref(T&& t, std::false_type) -> std::optional<FT>
{
return std::forward<T>(t);
}
template <typename FT, typename T>
auto deref(T&& t) -> std::optional<FT>;
template <typename FT, typename T>
auto deref(T&& t, std::true_type) -> std::optional<FT>
{
if (t)
{
return deref<FT>(*std::forward<T>(t));
}
return std::nullopt;
}
template <typename FT, typename T>
auto deref(T&& t) -> std::optional<FT>
{
return deref<FT>(std::forward<T>(t), can_dereference<T>{});
}
用法:
std::cout << deref<Entry>(container.entry).has_value() << std::endl;
std::cout << deref<Entry>(emptyContainer.entry).has_value() << std::endl;
输出量:
1
0
2条答案
按热度按时间t9aqgxwy1#
我可以向您推荐两种解决方案:
if_valid(value, thenLambda, elseLambda)
构造:1.具有then和else路径的一般解析器:(这样做的好处是,您可以简单地将
.name
和operator*
用作解析器)isr3a4wc2#
将recursive_dereference和convert_optional_fact结合起来,我最终得到的结果是:
输出量:
在Online GDB中使用它。
有了这个,我们就可以得到从容器到字段的一行:
我们得到一个可选的字符串“name”,如果没有设置,则为空可选。
仍然打开:
deref
中的最终类型,那么我们就不必在上面的行中指定deref<Entry>
中的Entry了。