libcopp 2.3.1
Loading...
Searching...
No Matches
callable_promise.h
Go to the documentation of this file.
1// Copyright 2023 owent
2
3#pragma once
4
5#include <libcopp/utils/config/libcopp_build_features.h>
6
7// clang-format off
8#include <libcopp/utils/config/stl_include_prefix.h> // NOLINT(build/include_order)
9// clang-format on
10#include <cstdlib>
11#include <type_traits>
12// clang-format off
13#include <libcopp/utils/config/stl_include_suffix.h> // NOLINT(build/include_order)
14// clang-format on
15
16#if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
17# include <exception>
18#endif
19
25
26#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE
27
28LIBCOPP_COPP_NAMESPACE_BEGIN
29
30template <class TFUTURE>
31class LIBCOPP_COPP_API_HEAD_ONLY some_delegate;
32
33template <class TVALUE, class TERROR_TRANSFORM>
34class LIBCOPP_COPP_API_HEAD_ONLY callable_future;
35
36template <class TVALUE, bool RETURN_VOID>
37class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_base;
38
39template <class TPROMISE, class TERROR_TRANSFORM, bool RETURN_VOID>
40class LIBCOPP_COPP_API_HEAD_ONLY callable_awaitable;
41
42template <class TVALUE>
43class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_base<TVALUE, true> : public promise_base_type {
44 public:
45 using value_type = TVALUE;
46
47 template <class... TARGS>
48 callable_promise_base(TARGS&&...) {}
49
50 callable_promise_base() = default;
51
52 void return_void() noexcept {
53 set_flag(promise_flag::kHasReturned, true);
54 if (get_status() < promise_status::kDone) {
55 set_status(promise_status::kDone);
56 }
57 }
58};
59
60template <class TVALUE, bool IS_DEFAULT_CONSTRUCTIBLE>
61class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_value_constructor;
62
63template <class TVALUE>
64class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_value_constructor<TVALUE, true> {
65 public:
66 template <class... TARGS>
67 inline static TVALUE construct(TARGS&&...) {
68 return TVALUE{};
69 }
70};
71
72template <class TVALUE>
73class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_value_constructor<TVALUE, false> {
74 public:
75 template <class... TARGS>
76 inline static TVALUE construct(TARGS&&... args) {
77 return TVALUE{std::forward<TARGS>(args)...};
78 }
79};
80
81template <class TVALUE>
82class LIBCOPP_COPP_API_HEAD_ONLY callable_promise_base<TVALUE, false> : public promise_base_type {
83 public:
84 using value_type = TVALUE;
85
86 template <class... TARGS>
87 callable_promise_base(TARGS&&... args)
88 : data_(callable_promise_value_constructor<value_type, !std::is_constructible<value_type, TARGS...>::value>::
89 construct(std::forward<TARGS>(args)...)) {}
90
91 void return_value(value_type value) {
92 set_flag(promise_flag::kHasReturned, true);
93 if (get_status() < promise_status::kDone) {
94 set_status(promise_status::kDone);
95 }
96 data_ = std::move(value);
97 }
98
99 LIBCOPP_UTIL_FORCEINLINE value_type& data() noexcept { return data_; }
100 LIBCOPP_UTIL_FORCEINLINE const value_type& data() const noexcept { return data_; }
101
102 protected:
103 value_type data_;
104};
105
106# if defined(LIBCOPP_MACRO_ENABLE_CONCEPTS) && LIBCOPP_MACRO_ENABLE_CONCEPTS
107template <DerivedPromiseBaseType TPROMISE>
108# else
109template <class TPROMISE, typename = std::enable_if_t<std::is_base_of<promise_base_type, TPROMISE>::value>>
110# endif
111class LIBCOPP_COPP_API_HEAD_ONLY callable_awaitable_base : public awaitable_base_type {
112 public:
113 using promise_type = TPROMISE;
114 using value_type = typename promise_type::value_type;
115 using handle_type = LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<promise_type>;
116
117 public:
118 callable_awaitable_base(handle_type handle) : callee_{handle} {}
119
120 LIBCOPP_UTIL_FORCEINLINE bool await_ready() noexcept {
121 if (!callee_) {
122 return true;
123 }
124
125 // callee_ should not be destroyed, this only works with callable_future<T>::promise_type
126 if (callee_.done()) {
127 return true;
128 }
129
130 if (callee_.promise().get_status() >= promise_status::kDone) {
131 return true;
132 }
133
134 if (callee_.promise().check_flag(promise_flag::kHasReturned)) {
135 return true;
136 }
137
138 return callee_.done();
139 }
140
141# if defined(LIBCOPP_MACRO_ENABLE_CONCEPTS) && LIBCOPP_MACRO_ENABLE_CONCEPTS
142 template <DerivedPromiseBaseType TCPROMISE>
143# else
144 template <class TCPROMISE, typename = std::enable_if_t<std::is_base_of<promise_base_type, TCPROMISE>::value>>
145# endif
146 inline bool await_suspend(LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<TCPROMISE> caller) noexcept {
147 if (caller.promise().get_status() < promise_status::kDone) {
148 set_caller(caller);
149 caller.promise().set_waiting_handle(callee_);
150 callee_.promise().add_caller(caller);
151
152 caller.promise().set_flag(promise_flag::kInternalWaitting, true);
153 return true;
154 } else {
155 // Already done and can not suspend again
156 auto& callee_promise = get_callee().promise();
157 // If callee is killed when running, we need inherit status from caller
158 if (callee_promise.get_status() < promise_status::kDone &&
159 callee_promise.check_flag(promise_flag::kInternalWaitting)) {
160 callee_promise.set_status(caller.promise().get_status());
161 callee_.resume();
162 }
163 // caller.resume();
164 return false;
165 }
166 }
167
168 LIBCOPP_UTIL_FORCEINLINE handle_type& get_callee() noexcept { return callee_; }
169 LIBCOPP_UTIL_FORCEINLINE const handle_type& get_callee() const noexcept { return callee_; }
170
171 protected:
172 void detach() noexcept {
173 // caller maybe null if the callable is already ready when co_await
174 auto caller = get_caller();
175 auto& callee_promise = get_callee().promise();
176
177 if (caller) {
178 if (nullptr != caller.promise) {
179 caller.promise->set_flag(promise_flag::kInternalWaitting, false);
180 caller.promise->set_waiting_handle(nullptr);
181 }
182 callee_promise.remove_caller(caller, true);
183 set_caller(nullptr);
184 }
185
186 if (callee_promise.get_status() < promise_status::kDone) {
187 if (!caller) {
188 callee_promise.set_status(promise_status::kKilled);
189 } else {
190 callee_promise.set_status(caller.promise->get_status());
191 }
192 }
193 }
194
195 private:
196 handle_type callee_;
197};
198
199template <class TPROMISE, class TERROR_TRANSFORM>
200class LIBCOPP_COPP_API_HEAD_ONLY callable_awaitable<TPROMISE, TERROR_TRANSFORM, true>
201 : public callable_awaitable_base<TPROMISE> {
202 public:
203 using base_type = callable_awaitable_base<TPROMISE>;
204 using promise_type = typename base_type::promise_type;
205 using value_type = typename base_type::value_type;
206 using handle_type = typename base_type::handle_type;
207
208 public:
209 using base_type::await_ready;
210 using base_type::await_suspend;
211 using base_type::detach;
212 using base_type::get_callee;
213 using base_type::get_caller;
214 using base_type::set_caller;
215 callable_awaitable(handle_type handle) : base_type(handle) {}
216
217 LIBCOPP_UTIL_FORCEINLINE void await_resume() {
218 detach();
219 get_callee().promise().resume_waiting(get_callee(), true);
220 }
221};
222
223template <class TPROMISE, class TERROR_TRANSFORM>
224class LIBCOPP_COPP_API_HEAD_ONLY callable_awaitable<TPROMISE, TERROR_TRANSFORM, false>
225 : public callable_awaitable_base<TPROMISE> {
226 public:
227 using base_type = callable_awaitable_base<TPROMISE>;
228 using promise_type = typename base_type::promise_type;
229 using value_type = typename base_type::value_type;
230 using handle_type = typename base_type::handle_type;
231
232 public:
233 using base_type::await_ready;
234 using base_type::await_suspend;
235 using base_type::detach;
236 using base_type::get_callee;
237 using base_type::get_caller;
238 using base_type::set_caller;
239 callable_awaitable(handle_type handle) : base_type(handle) {}
240
241 inline value_type await_resume() {
242 detach();
243 auto& callee_promise = get_callee().promise();
244 callee_promise.resume_waiting(get_callee(), true);
245
246 if (!callee_promise.check_flag(promise_flag::kHasReturned)) {
247 return TERROR_TRANSFORM()(callee_promise.get_status());
248 }
249
250 return std::move(callee_promise.data());
251 }
252};
253
254template <class TVALUE, class TERROR_TRANSFORM = promise_error_transform<TVALUE>>
255class LIBCOPP_COPP_API_HEAD_ONLY callable_future {
256 public:
257 using value_type = TVALUE;
258 using error_transform = TERROR_TRANSFORM;
259 using self_type = callable_future<value_type, error_transform>;
260 class promise_type
261 : public callable_promise_base<value_type, std::is_void<typename std::decay<value_type>::type>::value> {
262 public:
263# if defined(__GNUC__) && !defined(__clang__)
264 template <class... TARGS>
265 promise_type(TARGS&&... args)
266 : callable_promise_base<value_type, std::is_void<typename std::decay<value_type>::type>::value>(args...) {}
267# else
268 template <class... TARGS>
269 promise_type(TARGS&&... args)
270 : callable_promise_base<value_type, std::is_void<typename std::decay<value_type>::type>::value>(
271 std::forward<TARGS>(args)...) {}
272# endif
273
274 auto get_return_object() noexcept {
275 return self_type{LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<promise_type>::from_promise(*this)};
276 }
277
278 struct initial_awaitable {
279 inline bool await_ready() const noexcept { return false; }
280
281 inline void await_resume() const noexcept {
282 if (handle.promise().get_status() == promise_status::kCreated) {
283 promise_status excepted = promise_status::kCreated;
284 handle.promise().set_status(promise_status::kRunning, &excepted);
285 }
286 }
287
288 inline bool await_suspend(LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<promise_type> caller) noexcept {
289 handle = caller;
290
291 // Return false to resume the caller
292 return false;
293 }
294
295 LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<promise_type> handle;
296 };
297 initial_awaitable initial_suspend() noexcept { return {}; }
298# if defined(LIBCOPP_MACRO_ENABLE_EXCEPTION) && LIBCOPP_MACRO_ENABLE_EXCEPTION
299 void unhandled_exception() { throw; }
300# else
301 void unhandled_exception() { std::abort(); }
302# endif
303 };
304 using handle_type = LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<promise_type>;
305 using awaitable_type =
306 callable_awaitable<promise_type, error_transform, std::is_void<typename std::decay<value_type>::type>::value>;
307
308 public:
309 callable_future(handle_type handle) noexcept : current_handle_{handle} {}
310
311 callable_future(const callable_future&) = delete;
312 callable_future(callable_future&& other) noexcept {
313 current_handle_ = other.current_handle_;
314 other.current_handle_ = nullptr;
315 };
316
317 callable_future& operator=(const callable_future&) = delete;
318 callable_future& operator=(callable_future&& other) noexcept {
319 current_handle_ = other.current_handle_;
320 other.current_handle_ = nullptr;
321 return *this;
322 }
323
324 ~callable_future() {
325 // Move current_handle_ to stack here to allow recursive call of force_destroy
326 handle_type current_handle = current_handle_;
327 current_handle_ = nullptr;
328
329 while (current_handle && !current_handle.done() &&
330 !current_handle.promise().check_flag(promise_flag::kHasReturned)) {
331 if (current_handle.promise().get_status() < promise_status::kDone) {
332 current_handle.promise().set_status(promise_status::kKilled);
333 }
334 current_handle.resume();
335 }
336
337 if (current_handle) {
338 current_handle.promise().set_flag(promise_flag::kDestroying, true);
339 current_handle.destroy();
340 }
341 }
342
343 awaitable_type operator co_await() { return awaitable_type{current_handle_}; }
344
345 inline bool is_ready() const noexcept {
346 if (!current_handle_) {
347 return true;
348 }
349
350 return current_handle_.done() || current_handle_.promise().check_flag(promise_flag::kHasReturned);
351 }
352
353 LIBCOPP_UTIL_FORCEINLINE promise_status get_status() const noexcept { return current_handle_.promise().get_status(); }
354
355 static auto yield_status() noexcept { return promise_base_type::pick_current_status(); }
356
365 bool kill(promise_status target_status = promise_status::kKilled, bool force_resume = false) noexcept {
366 if (target_status < promise_status::kDone) {
367 return false;
368 }
369
370 bool ret = true;
371 while (true) {
372 if (!current_handle_) {
373 ret = false;
374 break;
375 }
376
377 if (current_handle_.done()) {
378 ret = false;
379 break;
380 }
381
382 promise_status current_status = get_status();
383 if (current_status >= promise_status::kDone) {
384 ret = false;
385 break;
386 }
387
388 if (!current_handle_.promise().set_status(target_status, &current_status)) {
389 continue;
390 }
391
392 if ((force_resume || current_handle_.promise().is_waiting()) &&
393 !current_handle_.promise().check_flag(promise_flag::kDestroying) &&
394 !current_handle_.promise().check_flag(promise_flag::kHasReturned)) {
395 // rethrow a exception in c++20 coroutine will crash when using MSVC now(VS2022)
396 // We may enable exception in the future
397# if 0 && defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
398 std::exception_ptr unhandled_exception;
399 try {
400# endif
401 current_handle_.resume();
402
403 // rethrow a exception in c++20 coroutine will crash when using MSVC now(VS2022)
404 // We may enable exception in the future
405# if 0 && defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
406 } catch (...) {
407 unhandled_exception = std::current_exception();
408 }
409 if (unhandled_exception) {
410 std::rethrow_exception(unhandled_exception);
411 }
412# endif
413 }
414 break;
415 }
416
417 return ret;
418 }
419
426 LIBCOPP_UTIL_FORCEINLINE const handle_type& get_internal_handle() const noexcept { return current_handle_; }
427
434 LIBCOPP_UTIL_FORCEINLINE handle_type& get_internal_handle() noexcept { return current_handle_; }
435
442 LIBCOPP_UTIL_FORCEINLINE const promise_type& get_internal_promise() const noexcept {
443 return current_handle_.promise();
444 }
445
452 LIBCOPP_UTIL_FORCEINLINE promise_type& get_internal_promise() noexcept { return current_handle_.promise(); }
453
454 private:
455 handle_type current_handle_;
456};
457
458// some delegate
459template <class TFUTURE>
460struct LIBCOPP_COPP_API_HEAD_ONLY some_delegate_context {
461 using future_type = TFUTURE;
462 using ready_output_type = typename some_ready<future_type>::type;
463
464 std::list<future_type*> pending;
465 ready_output_type ready;
466 size_t ready_bound = 0;
467 size_t scan_bound = 0;
468 promise_status status = promise_status::kCreated;
469 promise_caller_manager::handle_delegate caller_handle = promise_caller_manager::handle_delegate(nullptr);
470};
471
472template <class TFUTURE, class TDELEGATE_ACTION>
473class LIBCOPP_COPP_API_HEAD_ONLY some_delegate_base {
474 public:
475 using future_type = TFUTURE;
476 using value_type = typename future_type::value_type;
477 using context_type = some_delegate_context<future_type>;
478 using ready_output_type = typename context_type::ready_output_type;
479 using delegate_action_type = TDELEGATE_ACTION;
480
481 private:
482 static void force_resume_all(context_type& context) {
483 for (auto& pending_future : context.pending) {
484 delegate_action_type::resume_future(context.caller_handle, *pending_future);
485 }
486
487 if (context.status < promise_status::kDone && nullptr != context.caller_handle.promise) {
488 context.status = context.caller_handle.promise->get_status();
489 }
490
491 context.caller_handle = nullptr;
492 if (context.status < promise_status::kDone) {
493 context.status = promise_status::kKilled;
494 }
495 }
496
497 static void scan_ready(context_type& context) {
498 auto iter = context.pending.begin();
499
500 while (iter != context.pending.end()) {
501 if (delegate_action_type::is_pending(**iter)) {
502 ++iter;
503 continue;
504 }
505 future_type& future = **iter;
506 context.ready.push_back(gsl::make_not_null(&future));
507 iter = context.pending.erase(iter);
508
509 delegate_action_type::resume_future(context.caller_handle, future);
510 }
511 }
512
513 public:
514 class awaitable_type : public awaitable_base_type {
515 public:
516 awaitable_type(context_type* context) : context_(context) {}
517
518 inline bool await_ready() noexcept {
519 if (nullptr == context_) {
520 return true;
521 }
522
523 if (context_->status >= promise_status::kDone) {
524 return true;
525 }
526
527 return context_->pending.empty();
528 }
529
530# if defined(LIBCOPP_MACRO_ENABLE_CONCEPTS) && LIBCOPP_MACRO_ENABLE_CONCEPTS
531 template <DerivedPromiseBaseType TCPROMISE>
532# else
533 template <class TCPROMISE, typename = std::enable_if_t<std::is_base_of<promise_base_type, TCPROMISE>::value>>
534# endif
535 inline bool await_suspend(LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE coroutine_handle<TCPROMISE> caller) noexcept {
536 if (nullptr == context_ || caller.promise().get_status() >= promise_status::kDone) {
537 // Already done and can not suspend again
538 // caller.resume();
539 return false;
540 }
541
542 set_caller(caller);
543
544 // Allow kill resume to forward error information
545 caller.promise().set_flag(promise_flag::kInternalWaitting, true);
546
547 // set caller for all futures
548 if (!context_->caller_handle) {
549 context_->caller_handle = caller;
550 // Copy pending here, the callback may call resume and will change the pending list
551 std::list<future_type*> copy_pending = context_->pending;
552 for (auto& pending_future : copy_pending) {
553 delegate_action_type::suspend_future(context_->caller_handle, *pending_future);
554 }
555 }
556
557 return true;
558 }
559
560 void await_resume() {
561 // caller maybe null if the callable is already ready when co_await
562 auto caller = get_caller();
563 if (caller) {
564 if (nullptr != caller.promise) {
565 caller.promise->set_flag(promise_flag::kInternalWaitting, false);
566 }
567 set_caller(nullptr);
568 }
569
570 if (nullptr == context_) {
571 return;
572 }
573
574 ++context_->scan_bound;
575 if (context_->scan_bound >= context_->ready_bound) {
576 scan_ready(*context_);
577 context_->scan_bound = context_->ready.size();
578
579 if (context_->scan_bound >= context_->ready_bound && context_->status < promise_status::kDone) {
580 context_->status = promise_status::kDone;
581 }
582 }
583 }
584
585 private:
586 context_type* context_;
587 };
588
589 public:
590 struct promise_type {
591 context_type* context_;
592
593 promise_type(context_type* context) : context_(context) {}
594 promise_type(const promise_type&) = delete;
595 promise_type(promise_type&&) = delete;
596 promise_type& operator=(const promise_type&) = delete;
597 promise_type& operator=(promise_type&&) = delete;
598 ~promise_type() {
599 if LIBCOPP_UTIL_LIKELY_CONDITION (nullptr != context_ && !!context_->caller_handle) {
600 force_resume_all(*context_);
601 }
602 }
603
604 inline awaitable_type operator co_await() & { return awaitable_type{context_}; }
605 };
606
607 template <class TCONTAINER>
608 static callable_future<promise_status> run(ready_output_type& ready_futures, size_t ready_count,
609 TCONTAINER* futures) {
610 using container_type = typename std::decay<typename std::remove_pointer<TCONTAINER>::type>::type;
611 context_type context;
612 context.ready.reserve(gsl::size(*futures));
613
614 for (auto& future_object : *futures) {
615 auto& future_ref =
616 pick_some_reference<typename std::remove_reference<decltype(future_object)>::type>::unwrap(future_object);
617 if (delegate_action_type::is_pending(future_ref)) {
618 context.pending.push_back(&future_ref);
619 } else {
620 context.ready.push_back(gsl::make_not_null(&future_ref));
621 }
622 }
623
624 if (context.ready.size() >= ready_count) {
625 context.ready.swap(ready_futures);
626 co_return promise_status::kDone;
627 }
628
629 if (ready_count >= context.pending.size() + ready_futures.size()) {
630 ready_count = context.pending.size() + ready_futures.size();
631 }
632 context.ready_bound = ready_count;
633 context.scan_bound = context.ready.size();
634 context.status = promise_status::kRunning;
635
636 {
637 promise_type some_promise{&context};
638 while (context.status < promise_status::kDone) {
639 // Killed by caller
640 auto current_status = co_yield callable_future<promise_status>::yield_status();
641 if (current_status >= promise_status::kDone) {
642 context.status = current_status;
643 break;
644 }
645
646 co_await some_promise;
647 }
648
649 // destroy promise object and detach handles
650 }
651
652 context.ready.swap(ready_futures);
653 co_return context.status;
654 }
655
656 private:
657 LIBCOPP_COPP_NAMESPACE_ID::memory::default_strong_rc_ptr<context_type> context_;
658};
659
660// some
661template <class TVALUE, class TERROR_TRANSFORM>
662struct LIBCOPP_COPP_API_HEAD_ONLY some_delegate_callable_action {
663 using future_type = callable_future<TVALUE, TERROR_TRANSFORM>;
664 using context_type = some_delegate_context<future_type>;
665
666 inline static void suspend_future(const promise_caller_manager::handle_delegate& caller, future_type& callee) {
667 callee.get_internal_promise().add_caller(caller);
668 }
669
670 inline static void resume_future(const promise_caller_manager::handle_delegate& caller, future_type& callee) {
671 callee.get_internal_promise().remove_caller(caller, false);
672 // Do not force resume callee here, we allow to await the unready callable later.
673 }
674
675 inline static bool is_pending(future_type& future_object) noexcept {
676 auto future_status = future_object.get_status();
677 return future_status >= promise_status::kCreated && future_status < promise_status::kDone;
678 }
679};
680
681template <class TVALUE, class TERROR_TRANSFORM>
682class LIBCOPP_COPP_API_HEAD_ONLY some_delegate<callable_future<TVALUE, TERROR_TRANSFORM>>
683 : public some_delegate_base<callable_future<TVALUE, TERROR_TRANSFORM>,
684 some_delegate_callable_action<TVALUE, TERROR_TRANSFORM>> {
685 public:
686 using base_type = some_delegate_base<callable_future<TVALUE, TERROR_TRANSFORM>,
687 some_delegate_callable_action<TVALUE, TERROR_TRANSFORM>>;
688 using future_type = typename base_type::future_type;
689 using value_type = typename base_type::value_type;
690 using ready_output_type = typename base_type::ready_output_type;
691 using context_type = typename base_type::context_type;
692
693 using base_type::run;
694};
695
696LIBCOPP_COPP_NAMESPACE_END
697
698#endif
#define LIBCOPP_UTIL_FORCEINLINE
#define LIBCOPP_UTIL_LIKELY_CONDITION(__C)
#define LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE
Definition coroutine.h:41
auto make_not_null(T &&t) noexcept
Definition not_null.h:111
constexpr auto size(TCONTAINER &&container) -> decltype(container.size())
Definition span.h:44
constexpr auto data(TCONTAINER &&container) -> decltype(container.data())
Definition span.h:54
STL namespace.
std::shared_ptr< cli::cmd_option_value > value_type
Definition cmd_option.h:50