如何解决如何使用 Hana 使 std::optional 成为运行时 monad?
类型 hana::optional
代表一个
在编译时已知其可选性的可选值
并且它模拟了 Monad
的概念等。由于它是编译时可选的,如果我尝试 hana::flatten
非空 hana::optional
,在运行时会发生什么问题不包装另一个hana::optional
是没有意义的,因为错误会在编译时被捕获。
但是运行时可选呢?
这是我尝试制作 std::optional
(并且只是它,我没有制作可能的一般概念,它也 boost::optional
会满足)hana::Monad
,并且我'想知道这是否是正确的做法:
#include <assert.h>
#include <boost/hana/chain.hpp>
#include <optional>
#include <stdexcept>
#include <type_traits>
namespace hana = boost::hana;
template<typename T,typename = void>
struct has_value_type : std::false_type {};
template<typename T>
struct has_value_type<T,std::void_t<typename T::value_type>> : std::true_type {};
template<typename T>
bool constexpr has_value_type_t = has_value_type<T>::value;
namespace boost::hana {
template<typename T>
struct transform_impl<std::optional<T>> {
template <typename Opt,typename F>
static constexpr auto apply(Opt&& o,F&& f) {
return o.has_value() ? std::make_optional(f(o.value())) : std::nullopt;
}
};
template<typename T>
struct flatten_impl<std::optional<T>> {
template <typename X>
static constexpr auto apply(X&& x) {
if constexpr (has_value_type_t<typename X::value_type>)
return (x.has_value() && x.value().has_value()) ? x.value() : std::nullopt;
else {
if (!x.has_value())
return std::nullopt;
else
throw std::runtime_error("std::optional<non_stdoptional_type> can't be flattened!");
}
}
};
template<>
struct flatten_impl<std::nullopt_t> {
template <typename X>
static constexpr auto apply(X&&) {
return std::nullopt;
}
};
}
int main() {
assert(hana::flatten(std::optional<std::optional<int>>{3}) == std::optional<int>{3});
assert(hana::flatten(std::optional<int>{}) == std::optional<int>{});
assert(hana::flatten(std::nullopt) == std::optional<int>{});
//assert(hana::flatten(std::optional<int>{2}) == std::optional<int>{}); // throws
}
解决方法
可以进行一些改进。
Boost.Hana 使用标签调度系统来专门化 impl 结构。这简化了特化,但也防止了外部 impl 结构与内部函数模板的实例化的笛卡尔积。
此外,鉴于满足此约束的广泛类型集,您使用 value_type
检测嵌套的可选项也会出错。如果您使用 hana::is_a<optional_tag,X>
,标签系统也会对此有所帮助。
这个更具体的约束还消除了您对嵌套可选项执行运行时检查的需要,因为信息在类型本身中。 optional<optional<...>>
专门针对 flatten_impl<nullopt_t>
是不正确的,因为您无法通过 transform
运行它。我认为没有必要。
这是一个简化版本,也演示了您可以转换类型。 它也是 constexpr!
#include <assert.h>
#include <boost/hana/core/is_a.hpp>
#include <boost/hana/core/tag_of.hpp>
#include <boost/hana/flatten.hpp>
#include <optional>
#include <string>
namespace hana = boost::hana;
struct my_optional_tag { };
namespace boost::hana {
template <typename T>
struct tag_of<std::optional<T>> {
using type = my_optional_tag;
};
template <>
struct transform_impl<my_optional_tag> {
template <typename Opt,typename F>
static constexpr auto apply(Opt&& o,F&& f) {
using ResultValue = decltype(std::forward<F>(f)(std::forward<Opt>(o).value()));
using Result = std::optional<std::decay_t<ResultValue>>;
return o.has_value() ? Result(std::forward<F>(f)(std::forward<Opt>(o).value())) :
Result(std::nullopt);
}
};
template <>
struct flatten_impl<my_optional_tag> {
template <typename X>
static constexpr auto apply(X&& x) {
using Nested = decltype(std::forward<X>(x).value());
static_assert(is_a<my_optional_tag,Nested>,"must have nested optional");
return std::forward<X>(x).value_or(Nested(std::nullopt));
}
};
}
int main() {
// Functor
static_assert(hana::transform(std::optional<int>{41},[](int x) { return x + 1.0f; }) ==
std::optional<float>{42});
// Monad
static_assert(hana::flatten(std::optional<std::optional<int>>{3}) == std::optional<int>{3});
constexpr auto inc = [](int x) { return std::optional{x + 1.0f}; };
static_assert(hana::chain(std::optional<int>{5},inc) == std::optional{6.0f});
static_assert(hana::flatten(std::optional<std::optional<int>>{std::nullopt}) == std::nullopt);
// hana::flatten(std::optional<int>{3}); // compile-time error
}
请注意,我省略了 hana::lift
和 hana::ap
的实现,它们是 hana::Applicative
所必需的,而 hana::Monad
是必需的(在记录的 minimal complete definition )。我会把这个留给读者。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。