1 #pragma once
2
3 /// \file Expected.h
4 /// \brief Wrapper of type containing either a value or an error message
5 /// \author Pavel Sevecek (sevecek at sirrah.troja.mff.cuni.cz)
6 /// \date 2016-2021
7
8 #include "objects/containers/String.h"
9 #include "objects/wrappers/Variant.h"
10
11 NAMESPACE_SPH_BEGIN
12
13 struct UnexpectedTag {};
14 const UnexpectedTag UNEXPECTED;
15
16 /// \brief Wrapper of type that either contains a value of given type, or an error message.
17 ///
18 /// Expected is designed as a return type. When talking about 'expected' value, it means no error has been
19 /// encounter and Expected contains value of given type; 'unexpected' value means that Expected contains an
20 /// error message.
21 ///
22 /// Inspired by Andrei Alexandrescu - Systematic Error Handling in C++
23 /// https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
24 template <typename Type, typename Error = String>
25 class Expected {
26 private:
27 /// Wrapper to avoid issues if the value type is the same as the error
28 struct UnexpectedWrapper {
29 Error error;
30 };
31
32 // first type is dummy to use the default constructor of variant
33 Variant<NothingType, Type, UnexpectedWrapper> data;
34
35 public:
36 /// \brief Construct the expected value using default constructor.
37 ///
38 /// Should be avoided if possible as Expected is mainly designed as a value returned from function, but
39 /// nevertheless the default constructor is defined for convenience.
Expected()40 Expected() {
41 data.template emplace<Type>();
42 }
43
44 /// Constructs an expected value.
45 template <typename T, typename = std::enable_if_t<std::is_constructible<Type, T>::value>>
Expected(T && value)46 Expected(T&& value) {
47 data.template emplace<Type>(std::forward<T>(value));
48 }
49
50 /// Constructs an unexpected value.
51 template <typename TError, typename = std::enable_if_t<std::is_constructible<Error, TError>::value>>
Expected(UnexpectedTag,TError && error)52 Expected(UnexpectedTag, TError&& error) {
53 data.template emplace<UnexpectedWrapper>(UnexpectedWrapper{ std::forward<TError>(error) });
54 }
55
56 /// Conversion to bool, checking whether object constains expected value.
57 explicit operator bool() const {
58 return isExpected();
59 }
60
61 /// Negation operator, returns true if object does NOT contain expected value.
62 bool operator!() const {
63 return !isExpected();
64 }
65
66 /// \brief Returns the reference to expected value.
67 ///
68 /// Object must not contain unexpected value, checked by assert.
value()69 Type& value() {
70 SPH_ASSERT(isExpected());
71 return data.template get<Type>();
72 }
73
74 /// \brief Returns the const reference to expected value.
75 ///
76 /// Object must not contain unexpected value, checked by assert.
value()77 const Type& value() const {
78 SPH_ASSERT(isExpected());
79 return data.template get<Type>();
80 }
81
82 /// \brief Returns the expected value or given alternative if the object contains unexpected value.
valueOr(const Type & other)83 Type valueOr(const Type& other) const {
84 if (isExpected()) {
85 return this->value();
86 } else {
87 return other;
88 }
89 }
90
91 /// \brief Returns the error message.
92 ///
93 /// Object must contain unexpected value, checked by assert.
error()94 const Error& error() const {
95 SPH_ASSERT(!isExpected());
96 return data.template get<UnexpectedWrapper>().error;
97 }
98
99 /// \brief Operator -> for convenient access to member variables and functions of expected value.
100 ///
101 /// If the object contains unexpected, throws an assert.
102 Type* operator->() {
103 SPH_ASSERT(isExpected());
104 return &value();
105 }
106
107 /// \copydoc Type* operator->()
108 const Type* operator->() const {
109 SPH_ASSERT(isExpected());
110 return &value();
111 }
112
113 private:
isExpected()114 bool isExpected() const {
115 return data.getTypeIdx() == 1;
116 }
117 };
118
119 /// \brief Prints the expected value into a stream.
120 ///
121 /// If the object contains an expected value, prints the value into the stream, otherwise print the error
122 /// message. Enabled only if the wrapped type defines the operator<<, to that it corretly works with Catch
123 /// framework.
124 template <typename T, typename = std::enable_if_t<HasStreamOperator<T, std::wostream>::value>>
125 std::wostream& operator<<(std::wostream& stream, const Expected<T>& expected) {
126 if (expected) {
127 stream << expected.value();
128 } else {
129 stream << expected.error();
130 }
131 return stream;
132 }
133
134 /// \brief Constructs an unexpected value of given type, given error message as String.
135 ///
136 /// For other type of error messages, use constructor of Expected.
137 template <typename Type, typename... TArgs>
makeUnexpected(const String & error,const TArgs &...args)138 Expected<Type> makeUnexpected(const String& error, const TArgs&... args) {
139 return Expected<Type>(UNEXPECTED, format(error, args...));
140 }
141
142 NAMESPACE_SPH_END
143