1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_IO_RESULT_HPP
10  
#ifndef BOOST_CAPY_IO_RESULT_HPP
11  
#define BOOST_CAPY_IO_RESULT_HPP
11  
#define BOOST_CAPY_IO_RESULT_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <system_error>
14  
#include <system_error>
15  

15  

16  
#include <cstddef>
16  
#include <cstddef>
 
17 +
#include <tuple>
17  
#include <type_traits>
18  
#include <type_traits>
18  
#include <utility>
19  
#include <utility>
19  

20  

20  
namespace boost {
21  
namespace boost {
21  
namespace capy {
22  
namespace capy {
22  

23  

23  
/** Result type for asynchronous I/O operations.
24  
/** Result type for asynchronous I/O operations.
24  

25  

25  
    This template provides a unified result type for async operations,
26  
    This template provides a unified result type for async operations,
26  
    always containing a `std::error_code` plus optional additional
27  
    always containing a `std::error_code` plus optional additional
27 -
    values. It supports structured bindings.
28 +
    values. It supports structured bindings via the tuple protocol.
28  

29  

29  
    @par Example
30  
    @par Example
30  
    @code
31  
    @code
31  
    auto [ec, n] = co_await s.read_some(buf);
32  
    auto [ec, n] = co_await s.read_some(buf);
32  
    if (ec) { ... }
33  
    if (ec) { ... }
33  
    @endcode
34  
    @endcode
34  

35  

35 -
    @note Only 0, 1, 2, and 3 payload specializations are
36 +
    @note Payload members are only meaningful when
36 -
        provided. Payload members are only meaningful when
 
37  
        `ec` does not indicate an error.
37  
        `ec` does not indicate an error.
38  

38  

39 -
    @tparam Args Ordered payload types following the leading
39 +
    @tparam Ts Ordered payload types following the leading
40  
        `std::error_code`.
40  
        `std::error_code`.
41  
*/
41  
*/
42 -
template<class... Args>
42 +
template<class... Ts>
43 -
struct io_result
43 +
struct [[nodiscard]] io_result
44 -
{
 
45 -
    static_assert("io_result only supports up to 3 template arguments");
 
46 -
};
 
47 -

 
48 -
/** Result type for void operations.
 
49 -

 
50 -
    Used by operations like `connect()` that don't return a value
 
51 -
    beyond success/failure. This specialization is not an aggregate
 
52 -
    to enable implicit conversion from `error_code`.
 
53 -

 
54 -
    @par Example
 
55 -
    @code
 
56 -
    auto [ec] = co_await s.connect(ep);
 
57 -
    if (ec) { ... }
 
58 -
    @endcode
 
59 -
*/
 
60 -
template<>
 
61 -
struct [[nodiscard]] io_result<>
 
62 -
{
 
63 -
    /** The error code from the operation. */
 
64 -
    std::error_code ec;
 
65 -

 
66 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
67 -
    // Tuple protocol (unconditional - io_result<> is not an aggregate)
 
68 -
    template<std::size_t I>
 
69 -
    auto& get() & noexcept
 
70 -
    {
 
71 -
        static_assert(I == 0, "index out of range");
 
72 -
        return ec;
 
73 -
    }
 
74 -

 
75 -
    template<std::size_t I>
 
76 -
    auto const& get() const& noexcept
 
77 -
    {
 
78 -
        static_assert(I == 0, "index out of range");
 
79 -
        return ec;
 
80 -
    }
 
81 -

 
82 -
    template<std::size_t I>
 
83 -
    auto&& get() && noexcept
 
84 -
    {
 
85 -
        static_assert(I == 0, "index out of range");
 
86 -
        return std::move(ec);
 
87 -
    }
 
88 -
#endif
 
89 -
};
 
90 -

 
91 -
/** Result type for byte transfer operations.
 
92 -

 
93 -
    Used by operations like `read_some()` and `write_some()` that
 
94 -
    return the number of bytes transferred.
 
95 -

 
96 -
    @par Example
 
97 -
    @code
 
98 -
    auto [ec, n] = co_await s.read_some(buf);
 
99 -
    if (ec) { ... }
 
100 -
    @endcode
 
101 -
*/
 
102 -
template<typename T1>
 
103 -
struct [[nodiscard]] io_result<T1>
 
104 -
{
 
105 -
    /// The error code from the operation.
 
106 -
    std::error_code ec;
 
107 -

 
108 -
    /// The first payload value. Unspecified when `ec` is set.
 
109 -
    T1 t1{};
 
110 -

 
111 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
112 -
    template<std::size_t I>
 
113 -
    auto& get() & noexcept
 
114 -
    {
 
115 -
        static_assert(I < 2, "index out of range");
 
116 -
        if constexpr (I == 0) return ec;
 
117 -
        else return t1;
 
118 -
    }
 
119 -

 
120 -
    template<std::size_t I>
 
121 -
    auto const& get() const& noexcept
 
122 -
    {
 
123 -
        static_assert(I < 2, "index out of range");
 
124 -
        if constexpr (I == 0) return ec;
 
125 -
        else return t1;
 
126 -
    }
 
127 -

 
128 -
    template<std::size_t I>
 
129 -
    auto&& get() && noexcept
 
130 -
    {
 
131 -
        static_assert(I < 2, "index out of range");
 
132 -
        if constexpr (I == 0) return std::move(ec);
 
133 -
        else return std::move(t1);
 
134 -
    }
 
135 -
#endif
 
136 -
};
 
137 -

 
138 -
/** Result type for operations returning two values.
 
139 -

 
140 -
    Stores a leading `std::error_code` followed by two
 
141 -
    operation-specific payload values. Inspect `t1` and `t2`
 
142 -
    only when `ec` does not indicate an error.
 
143 -

 
144 -
    @par Example
 
145 -
    @code
 
146 -
    auto [ec, t1, t2] = co_await some_op();
 
147 -
    if (ec) { ... }
 
148 -
    @endcode
 
149 -

 
150 -
    @tparam T1 First payload type following the error code.
 
151 -
    @tparam T2 Second payload type following T1.
 
152 -

 
153 -
    @see io_result
 
154 -
*/
 
155 -
template<typename T1, typename T2>
 
156 -
struct [[nodiscard]] io_result<T1, T2>
 
157  
{
44  
{
158  
    /// The error code from the operation.
45  
    /// The error code from the operation.
159  
    std::error_code ec;
46  
    std::error_code ec;
160  

47  

161 -
    /// The first payload value. Unspecified when `ec` is set.
48 +
    /// The payload values. Unspecified when `ec` is set.
162 -
    T1 t1{};
49 +
    std::tuple<Ts...> values;
163 -

 
164 -
    /// The second payload value. Unspecified when `ec` is set.
 
165 -
    T2 t2{};
 
166 -

 
167 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
168 -
    template<std::size_t I>
 
169 -
    auto& get() & noexcept
 
170 -
    {
 
171 -
        static_assert(I < 3, "index out of range");
 
172 -
        if constexpr (I == 0) return ec;
 
173 -
        else if constexpr (I == 1) return t1;
 
174 -
        else return t2;
 
175 -
    }
 
176  

50  

177 -
    template<std::size_t I>
51 +
    /// Construct a default io_result.
178 -
    auto const& get() const& noexcept
52 +
    io_result() = default;
179 -
    {
 
180 -
        static_assert(I < 3, "index out of range");
 
181 -
        if constexpr (I == 0) return ec;
 
182 -
        else if constexpr (I == 1) return t1;
 
183 -
        else return t2;
 
184 -
    }
 
185  

53  

186 -
    template<std::size_t I>
54 +
    /// Construct from an error code and payload values.
187 -
    auto&& get() && noexcept
55 +
    io_result(std::error_code ec_, Ts... ts)
 
56 +
        : ec(ec_)
 
57 +
        , values(std::move(ts)...)
188 -
        static_assert(I < 3, "index out of range");
 
189 -
        if constexpr (I == 0) return std::move(ec);
 
190 -
        else if constexpr (I == 1) return std::move(t1);
 
191 -
        else return std::move(t2);
 
192  
    {
58  
    {
193 -
#endif
 
194 -
};
 
195 -

 
196 -
/** Result type for operations returning three values.
 
197  
    }
59  
    }
198  

60  

199 -
    Stores a leading `std::error_code` followed by three
61 +
    /// @cond
200 -
    operation-specific payload values. Inspect `t1`, `t2`,
 
201 -
    and `t3` only when `ec` does not indicate an error.
 
202 -

 
203 -
    @par Example
 
204 -
    @code
 
205 -
    auto [ec, t1, t2, t3] = co_await some_op();
 
206 -
    if (ec) { ... }
 
207 -
    @endcode
 
208 -

 
209 -
    @tparam T1 First payload type following the error code.
 
210 -
    @tparam T2 Second payload type following T1.
 
211 -
    @tparam T3 Third payload type following T2.
 
212 -

 
213 -
    @see io_result
 
214 -
*/
 
215 -
template<typename T1, typename T2, typename T3>
 
216 -
struct [[nodiscard]] io_result<T1, T2, T3>
 
217 -
{
 
218 -
    /// The error code from the operation.
 
219 -
    std::error_code ec;
 
220 -

 
221 -
    /// The first payload value. Unspecified when `ec` is set.
 
222 -
    T1 t1{};
 
223 -

 
224 -
    /// The second payload value. Unspecified when `ec` is set.
 
225 -
    T2 t2{};
 
226 -

 
227 -
    /// The third payload value. Unspecified when `ec` is set.
 
228 -
    T3 t3{};
 
229 -

 
230 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
231  
    template<std::size_t I>
62  
    template<std::size_t I>
232 -
    auto& get() & noexcept
63 +
    decltype(auto) get() & noexcept
233  
    {
64  
    {
234 -
        static_assert(I < 4, "index out of range");
65 +
        static_assert(I < 1 + sizeof...(Ts), "index out of range");
235 -
        if constexpr (I == 0) return ec;
66 +
        if constexpr (I == 0) return (ec);
236 -
        else if constexpr (I == 1) return t1;
67 +
        else return std::get<I - 1>(values);
237 -
        else if constexpr (I == 2) return t2;
 
238 -
        else return t3;
 
239  
    }
68  
    }
240  

69  

241  
    template<std::size_t I>
70  
    template<std::size_t I>
242 -
    auto const& get() const& noexcept
71 +
    decltype(auto) get() const& noexcept
243  
    {
72  
    {
244 -
        static_assert(I < 4, "index out of range");
73 +
        static_assert(I < 1 + sizeof...(Ts), "index out of range");
245 -
        if constexpr (I == 0) return ec;
74 +
        if constexpr (I == 0) return (ec);
246 -
        else if constexpr (I == 1) return t1;
75 +
        else return std::get<I - 1>(values);
247 -
        else if constexpr (I == 2) return t2;
 
248 -
        else return t3;
 
249  
    }
76  
    }
250  

77  

251  
    template<std::size_t I>
78  
    template<std::size_t I>
252 -
    auto&& get() && noexcept
79 +
    decltype(auto) get() && noexcept
253  
    {
80  
    {
254 -
        static_assert(I < 4, "index out of range");
81 +
        static_assert(I < 1 + sizeof...(Ts), "index out of range");
255  
        if constexpr (I == 0) return std::move(ec);
82  
        if constexpr (I == 0) return std::move(ec);
256 -
        else if constexpr (I == 1) return std::move(t1);
83 +
        else return std::get<I - 1>(std::move(values));
257 -
        else if constexpr (I == 2) return std::move(t2);
 
258 -
        else return std::move(t3);
 
259  
    }
84  
    }
260 -
#endif
85 +
    /// @endcond
261  
};
86  
};
262 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
263 -

 
264 -
// Free-standing get() overloads for ADL (MSVC aggregate workaround).
 
265  

87  

266  
/// @cond
88  
/// @cond
267 -

89 +
template<std::size_t I, class... Ts>
268 -
template<std::size_t I>
90 +
decltype(auto) get(io_result<Ts...>& r) noexcept
269 -
auto& get(io_result<>& r) noexcept
 
270 -
{
 
271 -
    return r.template get<I>();
 
272 -
}
 
273 -

 
274 -
template<std::size_t I>
 
275 -
auto const& get(io_result<> const& r) noexcept
 
276 -
{
 
277 -
    return r.template get<I>();
 
278 -
}
 
279 -

 
280 -
template<std::size_t I>
 
281 -
auto&& get(io_result<>&& r) noexcept
 
282 -
{
 
283 -
    return std::move(r).template get<I>();
 
284 -
}
 
285 -

 
286 -
template<std::size_t I, typename T1>
 
287 -
auto& get(io_result<T1>& r) noexcept
 
288 -
{
 
289 -
    return r.template get<I>();
 
290 -
}
 
291 -

 
292 -
template<std::size_t I, typename T1>
 
293 -
auto const& get(io_result<T1> const& r) noexcept
 
294 -
{
 
295 -
    return r.template get<I>();
 
296 -
}
 
297 -

 
298 -
template<std::size_t I, typename T1>
 
299 -
auto&& get(io_result<T1>&& r) noexcept
 
300 -
{
 
301 -
    return std::move(r).template get<I>();
 
302 -
}
 
303 -

 
304 -
template<std::size_t I, typename T1, typename T2>
 
305 -
auto& get(io_result<T1, T2>& r) noexcept
 
306 -
{
 
307 -
    return r.template get<I>();
 
308 -
}
 
309 -

 
310 -
template<std::size_t I, typename T1, typename T2>
 
311 -
auto const& get(io_result<T1, T2> const& r) noexcept
 
312 -
{
 
313 -
    return r.template get<I>();
 
314 -
}
 
315 -

 
316 -
template<std::size_t I, typename T1, typename T2>
 
317 -
auto&& get(io_result<T1, T2>&& r) noexcept
 
318 -
{
 
319 -
    return std::move(r).template get<I>();
 
320 -
}
 
321 -

 
322 -
template<std::size_t I, typename T1, typename T2, typename T3>
 
323 -
auto& get(io_result<T1, T2, T3>& r) noexcept
 
324  
{
91  
{
325  
    return r.template get<I>();
92  
    return r.template get<I>();
326  
}
93  
}
327  

94  

328 -
template<std::size_t I, typename T1, typename T2, typename T3>
95 +
template<std::size_t I, class... Ts>
329 -
auto const& get(io_result<T1, T2, T3> const& r) noexcept
96 +
decltype(auto) get(io_result<Ts...> const& r) noexcept
330  
{
97  
{
331  
    return r.template get<I>();
98  
    return r.template get<I>();
332  
}
99  
}
333  

100  

334 -
template<std::size_t I, typename T1, typename T2, typename T3>
101 +
template<std::size_t I, class... Ts>
335 -
auto&& get(io_result<T1, T2, T3>&& r) noexcept
102 +
decltype(auto) get(io_result<Ts...>&& r) noexcept
336  
{
103  
{
337  
    return std::move(r).template get<I>();
104  
    return std::move(r).template get<I>();
338 -

 
339  
}
105  
}
340  
/// @endcond
106  
/// @endcond
341 -
#endif // BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
342 -

 
343  

107  

344  
} // namespace capy
108  
} // namespace capy
345  
} // namespace boost
109  
} // namespace boost
346  

110  

347 -
#if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
111 +
// Tuple protocol for structured bindings
348 -

 
349 -
// Tuple protocol for structured bindings (MSVC workaround)
 
350 -
// MSVC has a bug with aggregate decomposition in coroutines, so we use
 
351 -
// tuple protocol instead which forces the compiler to use get<>() functions.
 
352 -

 
353  
namespace std {
112  
namespace std {
354  

113  

355 -
template<>
114 +
template<class... Ts>
356 -
struct tuple_size<boost::capy::io_result<>>
115 +
struct tuple_size<boost::capy::io_result<Ts...>>
357 -
    : std::integral_constant<std::size_t, 1> {};
116 +
    : std::integral_constant<std::size_t, 1 + sizeof...(Ts)> {};
358 -

 
359 -
template<>
 
360 -
struct tuple_element<0, boost::capy::io_result<>>
 
361 -
{
 
362 -
    using type = ::std::error_code;
 
363 -
};
 
364 -

 
365 -
template<typename T1>
 
366 -
struct tuple_size<boost::capy::io_result<T1>>
 
367 -
    : std::integral_constant<std::size_t, 2> {};
 
368 -

 
369 -
template<typename T1, typename T2>
 
370 -
struct tuple_size<boost::capy::io_result<T1, T2>>
 
371 -
    : std::integral_constant<std::size_t, 3> {};
 
372 -

 
373 -
template<typename T1, typename T2, typename T3>
 
374 -
struct tuple_size<boost::capy::io_result<T1, T2, T3>>
 
375 -
    : std::integral_constant<std::size_t, 4> {};
 
376 -

 
377 -
// tuple_element specializations for io_result<T1>
 
378 -

 
379 -
template<>
 
380 -
struct tuple_element<0, boost::capy::io_result<std::size_t>>
 
381 -
{
 
382 -
    using type = ::std::error_code;
 
383 -
};
 
384 -

 
385 -
template<>
 
386 -
struct tuple_element<1, boost::capy::io_result<std::size_t>>
 
387 -
{
 
388 -
    using type = std::size_t;
 
389 -
};
 
390 -

 
391 -
template<typename T1>
 
392 -
struct tuple_element<0, boost::capy::io_result<T1>>
 
393 -
{
 
394 -
    using type = ::std::error_code;
 
395 -
};
 
396 -

 
397 -
template<typename T1>
 
398 -
struct tuple_element<1, boost::capy::io_result<T1>>
 
399 -
{
 
400 -
    using type = T1;
 
401 -
};
 
402 -

 
403 -
// tuple_element specializations for io_result<T1, T2>
 
404 -

 
405 -
template<typename T1, typename T2>
 
406 -
struct tuple_element<0, boost::capy::io_result<T1, T2>>
 
407 -
{
 
408 -
    using type = ::std::error_code;
 
409 -
};
 
410 -

 
411 -
template<typename T1, typename T2>
 
412 -
struct tuple_element<1, boost::capy::io_result<T1, T2>>
 
413 -
{
 
414 -
    using type = T1;
 
415 -
};
 
416 -

 
417 -
template<typename T1, typename T2>
 
418 -
struct tuple_element<2, boost::capy::io_result<T1, T2>>
 
419 -
{
 
420 -
    using type = T2;
 
421 -
};
 
422 -

 
423 -
// tuple_element specializations for io_result<T1, T2, T3>
 
424 -

 
425 -
template<typename T1, typename T2, typename T3>
 
426 -
struct tuple_element<0, boost::capy::io_result<T1, T2, T3>>
 
427 -
{
 
428 -
    using type = ::std::error_code;
 
429 -
};
 
430 -

 
431 -
template<typename T1, typename T2, typename T3>
 
432 -
struct tuple_element<1, boost::capy::io_result<T1, T2, T3>>
 
433 -
{
 
434 -
    using type = T1;
 
435 -
};
 
436  

117  

437 -
template<typename T1, typename T2, typename T3>
118 +
template<class... Ts>
438 -
struct tuple_element<2, boost::capy::io_result<T1, T2, T3>>
119 +
struct tuple_element<0, boost::capy::io_result<Ts...>>
439  
{
120  
{
440 -
    using type = T2;
121 +
    using type = std::error_code;
441  
};
122  
};
442  

123  

443 -
template<typename T1, typename T2, typename T3>
124 +
template<std::size_t I, class... Ts>
444 -
struct tuple_element<3, boost::capy::io_result<T1, T2, T3>>
125 +
struct tuple_element<I, boost::capy::io_result<Ts...>>
445  
{
126  
{
446 -
    using type = T3;
127 +
    using type = std::tuple_element_t<I - 1, std::tuple<Ts...>>;
447  
};
128  
};
448  

129  

449 -

 
450 -
#endif // BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
 
451  
} // namespace std
130  
} // namespace std
452  

131  

453  
#endif // BOOST_CAPY_IO_RESULT_HPP
132  
#endif // BOOST_CAPY_IO_RESULT_HPP