Skip to content

Commit e1730cd

Browse files
author
Alex Trotta
committed
Add std::expected bindings
1 parent 9c18a74 commit e1730cd

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

include/pybind11/stl.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
# include <variant>
3333
#endif
3434

35+
#ifdef __cpp_lib_expected
36+
# include <expected>
37+
#endif
38+
3539
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
3640
PYBIND11_NAMESPACE_BEGIN(detail)
3741

@@ -424,6 +428,47 @@ struct variant_caster<V<Ts...>> {
424428
+ const_name("]"));
425429
};
426430

431+
/// Generic expected caster
432+
template <typename E>
433+
struct expected_caster;
434+
435+
template <template <typename, typename> class E, typename TValue, typename TError>
436+
struct expected_caster<E<TValue, TError>> {
437+
using expected_type = E<TValue, TError>;
438+
439+
// I'm not sure what we should call this, as we never actually hold it
440+
// on the python side, it's exclusively deconstructed. In our case, we really
441+
// only care about the conversion from C++ to Python.
442+
// Maybe something like `Union[TValue, TError]`?
443+
PYBIND11_TYPE_CASTER(expected_type, pybind11::detail::const_name("expected"));
444+
445+
// load() is not implemented - we are only interested in unwrapping expected
446+
// types from C++, not re-wrapping them.
447+
448+
// This is templated so that it takes a universal reference rather than an
449+
// rvalue reference.
450+
static pybind11::handle cast(std::same_as<expected_type> auto &&src,
451+
pybind11::return_value_policy policy,
452+
pybind11::handle parent) {
453+
if (src.has_value()) {
454+
return pybind11::detail::make_caster<TValue>::cast(
455+
std::forward<expected_type>(src).value(), policy, parent);
456+
} else {
457+
// The goal here is to defer to __repr__ for consistent formatting with
458+
// Python
459+
if constexpr (std::same_as<TError, std::string>) {
460+
// Optimize in this case, no need to convert to python to get its
461+
// representation
462+
throw std::runtime_error(std::forward<expected_type>(src).error());
463+
} else {
464+
auto str = pybind11::detail::make_caster<TError>::cast(
465+
std::forward<expected_type>(src).error(), policy, parent);
466+
throw std::runtime_error(pybind11::repr(std::move(str)));
467+
}
468+
}
469+
}
470+
};
471+
427472
#if defined(PYBIND11_HAS_VARIANT)
428473
template <typename... Ts>
429474
struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> {};
@@ -432,6 +477,12 @@ template <>
432477
struct type_caster<std::monostate> : public void_caster<std::monostate> {};
433478
#endif
434479

480+
#ifdef __cpp_lib_expected
481+
template <typename TValue, typename TError>
482+
struct pybind11::detail::type_caster<std::expected<TValue, TError>>
483+
: expected_caster<std::expected<TValue, TError>> {};
484+
#endif
485+
435486
PYBIND11_NAMESPACE_END(detail)
436487

437488
inline std::ostream &operator<<(std::ostream &os, const handle &obj) {

0 commit comments

Comments
 (0)