1/* Unit testing for outcomes
2(C) 2013-2024 Niall Douglas <http://www.nedproductions.biz/> (1 commit)
3
4
5Boost Software License - Version 1.0 - August 17th, 2003
6
7Permission is hereby granted, free of charge, to any person or organization
8obtaining a copy of the software and accompanying documentation covered by
9this license (the "Software") to use, reproduce, display, distribute,
10execute, and transmit the Software, and to prepare derivative works of the
11Software, and to permit third-parties to whom the Software is furnished to
12do so, all subject to the following:
13
14The copyright notices in the Software and this entire statement, including
15the above license grant, this restriction and the following disclaimer,
16must be included in all copies of the Software, in whole or in part, and
17all derivative works of the Software, unless such copies or derivative
18works are solely in the form of machine-executable object code generated by
19a source language processor.
20
21THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27DEALINGS IN THE SOFTWARE.
28*/
29
30#include <boost/outcome/result.hpp>
31#include <boost/outcome/try.hpp>
32#include <boost/test/unit_test.hpp>
33#include <boost/test/unit_test_monitor.hpp>
34
35namespace issues244
36{
37 namespace outcome = BOOST_OUTCOME_V2_NAMESPACE;
38
39 static int counter = 0;
40 static std::vector<int> default_constructor, copy_constructor, move_constructor, destructor;
41 struct Foo
42 {
43 int x{2};
44 explicit Foo(int v)
45 : x(v)
46 {
47 std::cout << " Default constructor " << ++counter << std::endl;
48 default_constructor.push_back(x: counter);
49 }
50 Foo(const Foo &o) noexcept
51 : x(o.x)
52 {
53 std::cout << " Copy constructor " << ++counter << std::endl;
54 copy_constructor.push_back(x: counter);
55 }
56 Foo(Foo &&o) noexcept
57 : x(o.x)
58 {
59 std::cout << " Move constructor " << ++counter << std::endl;
60 move_constructor.push_back(x: counter);
61 }
62 ~Foo()
63 {
64 std::cout << " Destructor " << ++counter << std::endl;
65 destructor.push_back(x: counter);
66 }
67 };
68 struct Immovable
69 {
70 int x{2};
71 explicit Immovable(int v)
72 : x(v)
73 {
74 }
75 Immovable(const Immovable &) = delete;
76 Immovable(Immovable &&) = delete;
77 };
78
79 outcome::result<Foo> get_foo() noexcept { return outcome::result<Foo>(outcome::in_place_type<Foo>, 5); }
80
81 template <typename T> T &&filterR(T &&v) { return static_cast<T &&>(v); }
82 template <typename T> const T &filterL(T &&v) { return v; }
83} // namespace issues244
84
85
86BOOST_OUTCOME_AUTO_TEST_CASE(issues_0244_test, "TRY/TRYX has dangling reference if xvalues are emitted from tried expression")
87{
88 using namespace issues244;
89 auto check = [](const char *desc, auto &&f) {
90 counter = 0;
91 default_constructor.clear();
92 copy_constructor.clear();
93 move_constructor.clear();
94 destructor.clear();
95 std::cout << "\n" << desc << std::endl;
96 auto r = f();
97 std::cout << " Check integer " << ++counter << std::endl;
98 BOOST_REQUIRE(r);
99 BOOST_CHECK(r.value() == 5);
100 if(!copy_constructor.empty())
101 {
102 BOOST_CHECK(copy_constructor.front() < destructor.front());
103 }
104 else if(!move_constructor.empty())
105 {
106 BOOST_CHECK(move_constructor.front() < destructor.front());
107 }
108 };
109
110 /*
111 Default constructor 1 (bind expression prvalue to unique rvalue)
112 Move constructor 2 (move from unique rvalue to v value)
113 After TRY 3
114 Destructor 4 (destruct v value)
115 Destructor 5 (destruct lifetime extended unique rvalue)
116 Check integer 6
117 */
118 check("prvalue from expression with lifetime extension is moved into value", []() -> outcome::result<int> {
119 BOOST_OUTCOME_TRY(auto v, get_foo());
120 std::cout << " After TRY " << ++counter << std::endl;
121 return v.x;
122 });
123 /*
124 Default constructor 1 (bind expression prvalue to unique rvalue)
125 (bind unique rvalue to v rvalue)
126 After TRY 2
127 Destructor 3 (destruct lifetime extended unique rvalue)
128 Check integer 4
129 */
130 check("prvalue from expression with lifetime extension is bound into rvalue", []() -> outcome::result<int> {
131 BOOST_OUTCOME_TRY(auto &&v, get_foo());
132 std::cout << " After TRY " << ++counter << std::endl;
133 return v.x;
134 });
135
136 /*
137 Default constructor 1
138 Move constructor 2 (move expression xvalue into unique value)
139 Destructor 3 (destruct expression xvalue)
140 Move constructor 4 (move from unique value to v value)
141 After TRY 5
142 Destructor 6 (destruct v value)
143 Destructor 7 (destruct unique value)
144 Check integer 8
145 */
146 check("xvalue from expression without lifetime extension is moved into temporary and then moved into value", []() -> outcome::result<int> {
147 BOOST_OUTCOME_TRY(auto v, filterR(get_foo()));
148 std::cout << " After TRY " << ++counter << std::endl;
149 return v.x;
150 });
151 /*
152 Default constructor 1
153 Move constructor 2 (move expression xvalue into unique value)
154 Destructor 3 (destruct expression xvalue)
155 After TRY 4
156 Destructor 5 (destruct unique value)
157 Check integer 6
158 */
159 check("xvalue from expression without lifetime extension is moved into temporary and then bound into rvalue", []() -> outcome::result<int> {
160 BOOST_OUTCOME_TRY(auto &&v, filterR(get_foo()));
161 std::cout << " After TRY " << ++counter << std::endl;
162 return v.x;
163 });
164
165 /*
166 Default constructor 1
167 Copy constructor 2 (copy expression lvalue into unique value)
168 Destructor 3 (destruct expression lvalue)
169 Copy constructor 4 (copy from unique value to v value)
170 After TRY 5
171 Destructor 6 (destruct v value)
172 Destructor 7 (destruct unique value)
173 Check integer 8
174 */
175 check("lvalue from expression without lifetime extension is moved into temporary and then moved into value", []() -> outcome::result<int> {
176 BOOST_OUTCOME_TRY(auto v, filterL(get_foo()));
177 std::cout << " After TRY " << ++counter << std::endl;
178 return v.x;
179 });
180 /*
181 Default constructor 1
182 Copy constructor 2 (copy expression lvalue into unique value)
183 Destructor 3 (destruct expression lvalue)
184 After TRY 4
185 Destructor 5 (destruct unique value)
186 Check integer 6
187 */
188 check("lvalue from expression without lifetime extension is moved into temporary and then bound into rvalue", []() -> outcome::result<int> {
189 BOOST_OUTCOME_TRY(auto &&v, filterL(get_foo()));
190 std::cout << " After TRY " << ++counter << std::endl;
191 return v.x;
192 });
193
194
195 check("TRY lvalue passthrough", []() -> outcome::result<int> {
196 const auto &x = outcome::result<Immovable>(outcome::in_place_type<Immovable>, 5);
197 // Normally a lvalue input triggers value unique, which would fail to compile here
198 BOOST_OUTCOME_TRY((auto &, v), x);
199 return v.x;
200 });
201
202 // Force use of rvalue refs for unique and bound value
203 check("TRY rvalue passthrough", []() -> outcome::result<int> {
204 auto &&x = outcome::result<Immovable>(outcome::in_place_type<Immovable>, 5);
205 // Normally an xvalue input triggers value unique, which would fail to compile here
206 BOOST_OUTCOME_TRY((auto &&, v), x);
207 return v.x;
208 });
209
210 // Force use of lvalue refs for unique and bound value
211 check("TRY prvalue as lvalue passthrough", []() -> outcome::result<int> {
212 outcome::result<Immovable> i(outcome::in_place_type<Immovable>, 5);
213 BOOST_OUTCOME_TRY((auto &, v), i);
214 return v.x;
215 });
216
217 // Force use of rvalue refs for unique and bound value
218 check("TRY prvalue as rvalue passthrough", []() -> outcome::result<int> {
219 outcome::result<Immovable> i(outcome::in_place_type<Immovable>, 5);
220 BOOST_OUTCOME_TRY((auto &&, v), i);
221 return v.x;
222 });
223
224
225 check("TRYV lvalue passthrough", []() -> outcome::result<int> {
226 const auto &x = outcome::result<Immovable>(outcome::in_place_type<Immovable>, 5);
227 // Normally a lvalue input triggers value unique, which would fail to compile here
228 BOOST_OUTCOME_TRYV2(auto &, x);
229 return 5;
230 });
231
232 // Force use of rvalue refs for unique and bound value
233 check("TRYV rvalue passthrough", []() -> outcome::result<int> {
234 auto &&x = outcome::result<Immovable>(outcome::in_place_type<Immovable>, 5);
235 // Normally an xvalue input triggers value unique, which would fail to compile here
236 BOOST_OUTCOME_TRYV2(auto &&, x);
237 return 5;
238 });
239
240 // Force use of lvalue refs for unique and bound value
241 check("TRYV prvalue as lvalue passthrough", []() -> outcome::result<int> {
242 outcome::result<Immovable> i(outcome::in_place_type<Immovable>, 5);
243 BOOST_OUTCOME_TRYV2(auto &, i);
244 return 5;
245 });
246
247 // Force use of rvalue refs for unique and bound value
248 check("TRYV prvalue as rvalue passthrough", []() -> outcome::result<int> {
249 outcome::result<Immovable> i(outcome::in_place_type<Immovable>, 5);
250 BOOST_OUTCOME_TRYV2(auto &, i);
251 return 5;
252 });
253}
254

source code of boost/libs/outcome/test/tests/issue0244.cpp