Skip to content

Commit 8cc14f1

Browse files
committed
Add TryInto and try_into<T>()
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for #221.
1 parent 087cd3a commit 8cc14f1

File tree

5 files changed

+76
-19
lines changed

5 files changed

+76
-19
lines changed

subspace/construct/from.h

+5-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <concepts>
1818

19+
#include "subspace/mem/forward.h"
1920
#include "subspace/result/__private/is_result_type.h"
2021

2122
namespace sus::construct {
@@ -48,7 +49,7 @@ namespace sus::construct {
4849
/// ```
4950
template <class ToType, class FromType>
5051
concept From = requires(FromType&& from) {
51-
{ ToType::from(static_cast<FromType&&>(from)) } -> std::same_as<ToType>;
52+
{ ToType::from(::sus::forward<FromType>(from)) } -> std::same_as<ToType>;
5253
};
5354

5455
/// A concept that indicates `ToType` can be (sometimes) constructed from a
@@ -59,11 +60,9 @@ concept From = requires(FromType&& from) {
5960
/// `sus::Result`.
6061
template <class ToType, class FromType>
6162
concept TryFrom = requires(FromType&& from) {
62-
{ ToType::try_from(static_cast<FromType&&>(from)) };
63-
requires std::same_as<
64-
typename ::sus::result::__private::IsResultType<decltype(ToType::try_from(
65-
static_cast<FromType&&>(from)))>::ok_type,
66-
ToType>;
63+
{
64+
ToType::try_from(::sus::forward<FromType>(from))
65+
} -> ::sus::result::__private::IsResultWithSuccessType<ToType>;
6766
};
6867

6968
} // namespace sus::construct

subspace/construct/from_unittest.cc

+22
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
#include "subspace/construct/from.h"
1616

1717
#include "subspace/construct/into.h"
18+
#include "subspace/result/result.h"
1819

1920
using sus::construct::From;
21+
using sus::construct::TryFrom;
2022

2123
namespace {
2224

@@ -44,4 +46,24 @@ static_assert(!From<WithFromS, int>);
4446
static_assert(!From<FromReturnsVoid, int>);
4547
static_assert(!From<FromReturnsElse, int>);
4648

49+
enum Error {};
50+
51+
struct TryYes {
52+
static sus::Result<TryYes, Error> try_from(S) { return sus::ok(TryYes()); }
53+
};
54+
struct TryNoResult {
55+
static TryYes try_from(S) { return TryYes(); }
56+
};
57+
struct TryWrongResult {
58+
static sus::Result<S, Error> try_from(S) { return sus::ok(S()); }
59+
};
60+
struct TryVoidResult {
61+
static sus::Result<void, Error> try_from(S) { return sus::ok(); }
62+
};
63+
64+
static_assert(TryFrom<TryYes, S>);
65+
static_assert(!TryFrom<TryNoResult, S>);
66+
static_assert(!TryFrom<TryWrongResult, S>);
67+
static_assert(!TryFrom<TryVoidResult, S>);
68+
4769
} // namespace

subspace/construct/into.h

+36-13
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
#include "subspace/construct/__private/into_ref.h"
2323
#include "subspace/construct/from.h"
2424
#include "subspace/macros/compiler.h"
25+
#include "subspace/macros/lifetimebound.h"
26+
#include "subspace/mem/forward.h"
2527

2628
namespace sus::construct {
2729

28-
/// A concept that declares `FromType` can be converted to `ToType`, through the
30+
/// A concept that declares `FromType` can be converted to `ToType` through the
2931
/// `From` concept or through an identity transformation.
3032
///
3133
/// When true, `ToType::from(FromType)` can be used to construct `ToType`,
@@ -84,25 +86,22 @@ template <class FromType>
8486
requires(!std::is_const_v<std::remove_reference_t<FromType>> &&
8587
std::is_rvalue_reference_v<FromType &&> &&
8688
!std::is_array_v<FromType>)
87-
constexpr inline auto into(
88-
FromType&& from sus_if_clang([[clang::lifetimebound]])) noexcept {
89+
constexpr inline auto into(FromType&& from sus_lifetimebound) noexcept {
8990
return __private::IntoRef(
9091
static_cast<std::remove_reference_t<FromType>&&>(from));
9192
}
9293

9394
template <class FromType>
9495
requires(std::is_trivially_copyable_v<FromType> && !std::is_array_v<FromType>)
95-
constexpr inline auto into(
96-
const FromType& from sus_if_clang([[clang::lifetimebound]])) noexcept {
96+
constexpr inline auto into(const FromType& from sus_lifetimebound) noexcept {
9797
return __private::IntoRef<const FromType&>(from);
9898
}
9999

100100
template <class FromType>
101101
requires(!std::is_const_v<std::remove_reference_t<FromType>> &&
102102
std::is_trivially_move_constructible_v<FromType> &&
103103
!std::is_array_v<FromType>)
104-
constexpr inline auto into(
105-
FromType& from sus_if_clang([[clang::lifetimebound]])) noexcept {
104+
constexpr inline auto into(FromType& from sus_lifetimebound) noexcept {
106105
return __private::IntoRef(
107106
static_cast<std::remove_reference_t<FromType>&&>(from));
108107
}
@@ -113,7 +112,7 @@ constexpr inline auto into(
113112
/// satisfied.
114113
template <class FromType, size_t N>
115114
constexpr inline auto array_into(
116-
FromType (&from)[N] sus_if_clang([[clang::lifetimebound]])) noexcept {
115+
FromType (&from)[N] sus_lifetimebound) noexcept {
117116
return __private::IntoRefArray<FromType, N>(from);
118117
}
119118

@@ -128,8 +127,7 @@ constexpr inline auto array_into(
128127
template <class FromType>
129128
requires(!std::is_const_v<std::remove_reference_t<FromType>> &&
130129
!std::is_array_v<FromType>)
131-
constexpr inline auto move_into(
132-
FromType& from sus_if_clang([[clang::lifetimebound]])) noexcept {
130+
constexpr inline auto move_into(FromType& from sus_lifetimebound) noexcept {
133131
return __private::IntoRef(
134132
static_cast<std::remove_cv_t<std::remove_reference_t<FromType>>&&>(from));
135133
}
@@ -138,13 +136,37 @@ template <class FromType>
138136
requires(std::is_rvalue_reference_v<FromType &&> &&
139137
!std::is_const_v<std::remove_reference_t<FromType>> &&
140138
!std::is_array_v<FromType>)
141-
constexpr inline auto move_into(
142-
FromType&& from sus_if_clang([[clang::lifetimebound]])) noexcept {
139+
constexpr inline auto move_into(FromType&& from sus_lifetimebound) noexcept {
143140
return __private::IntoRef(
144141
static_cast<std::remove_cv_t<std::remove_reference_t<FromType>>&&>(from));
145142
}
146143

147-
// TODO: Consider adding sus::try_into() and a TryInto concept?
144+
/// A concept that declares `FromType` can (sometimes) be converted to `ToType`
145+
/// through the `TryFrom` concept or through an identity transformation.
146+
template <class FromType, class ToType>
147+
concept TryInto = ::sus::construct::TryFrom<ToType, FromType> ||
148+
std::same_as<ToType, FromType>;
149+
150+
/// Attempts to convert from the given value to a `ToType`.
151+
///
152+
/// Unlike `into()`, this function can not use type deduction to determine the
153+
/// receiving type as it needs to determine the Result type and allow the caller
154+
/// the chance to handle the error condition.
155+
///
156+
/// The `TryFrom` concept requires a `try_from()` method that returns a
157+
/// `Result`. That `Result` will be the return type of this function.
158+
///
159+
/// # Example
160+
/// ```
161+
/// auto valid = sus::try_into<u8>(123_i32).unwrap_or_default();
162+
/// sus::check(valid == 123);
163+
/// auto invalid = sus::try_into<u8>(-1_i32).unwrap_or_default();
164+
/// sus::check(invalid == 0);
165+
/// ```
166+
template <class ToType, TryInto<ToType> FromType>
167+
constexpr inline auto try_into(FromType&& from) noexcept {
168+
return ToType::try_from(::sus::forward<FromType>(from));
169+
}
148170

149171
} // namespace sus::construct
150172

@@ -153,4 +175,5 @@ namespace sus {
153175
using ::sus::construct::array_into;
154176
using ::sus::construct::into;
155177
using ::sus::construct::move_into;
178+
using ::sus::construct::try_into;
156179
} // namespace sus

subspace/construct/into_unittest.cc

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "googletest/include/gtest/gtest.h"
1818
#include "subspace/macros/__private/compiler_bugs.h"
1919
#include "subspace/mem/forward.h"
20+
#include "subspace/prelude.h"
2021
#include "subspace/test/behaviour_types.h"
2122

2223
using sus::construct::From;
@@ -156,4 +157,11 @@ TEST(Into, Concept) {
156157
EXPECT_EQ(f(S(3)).got_value, 3);
157158
}
158159

160+
TEST(TryInto, Example) {
161+
auto valid = sus::try_into<u8>(123_i32).unwrap_or_default();
162+
sus::check(valid == 123u);
163+
auto invalid = sus::try_into<u8>(-1_i32).unwrap_or_default();
164+
sus::check(invalid == 0u);
165+
}
166+
159167
} // namespace

subspace/result/__private/is_result_type.h

+5
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ struct IsResultType<::sus::result::Result<U, V>> final : std::true_type {
3030
using err_type = V;
3131
};
3232

33+
template <class T, class Success>
34+
concept IsResultWithSuccessType =
35+
IsResultType<T>::value &&
36+
std::same_as<Success, typename IsResultType<T>::ok_type>;
37+
3338
} // namespace sus::result::__private

0 commit comments

Comments
 (0)