【发布时间】:2022-11-18 21:02:26
【问题描述】:
我想要一种优雅的方式来安全地读取包含在“可空类型”(例如 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;
}
要从给定容器的条目中读取“名称”字段,我可以这样写:
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 中的布尔值(可选和共享指针就是这种情况),这将允许我们检查它们是否已设置。如果它们已设置,我们可以继续取消引用递归。如果没有设置,我们可以中断递归并返回最终类型的空可选。
不幸的是,我无法将其表达为工作代码。该解决方案最多应限于“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
【问题讨论】:
-
你将如何处理错误?
-
此外,
std::optional<std::shared_ptr<T>>没有什么意义,除非你真的想区分这两种错误情况(你 - 根据帖子 - 不要):std::shared_ptr<T>也可以为空,因此表示可选性。 -
关于错误处理:在这种情况下,我要么有值,要么没有。 shared_ptr 或 optional 是否为空都不会改变这一事实。在重要的情况下,每个字段都必须单独测试。
-
“
std::optional<std::shared_ptr<T>>毫无意义”。在这种情况下,这是我们必须处理的。 -
if constexprand concepts 你的方法可以小得多
标签: c++ templates shared-ptr template-meta-programming stdoptional