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