1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4
5 #pragma once
6
7 #include <chrono>
8 #include <cmath>
9 #include <cstddef>
10 #include <cstdint>
11 #include <iosfwd>
12 #include <iterator>
13 #include <map>
14 #include <string>
15 #include <tuple>
16 #include <type_traits>
17 #include <vector>
18
19 #include "caf/config_value_reader.hpp"
20 #include "caf/config_value_writer.hpp"
21 #include "caf/detail/bounds_checker.hpp"
22 #include "caf/detail/core_export.hpp"
23 #include "caf/detail/move_if_not_ptr.hpp"
24 #include "caf/detail/parse.hpp"
25 #include "caf/detail/type_traits.hpp"
26 #include "caf/dictionary.hpp"
27 #include "caf/expected.hpp"
28 #include "caf/fwd.hpp"
29 #include "caf/inspector_access.hpp"
30 #include "caf/inspector_access_type.hpp"
31 #include "caf/optional.hpp"
32 #include "caf/raise_error.hpp"
33 #include "caf/string_algorithms.hpp"
34 #include "caf/string_view.hpp"
35 #include "caf/sum_type.hpp"
36 #include "caf/sum_type_access.hpp"
37 #include "caf/sum_type_token.hpp"
38 #include "caf/timespan.hpp"
39 #include "caf/timestamp.hpp"
40 #include "caf/uri.hpp"
41 #include "caf/variant.hpp"
42
43 namespace caf::detail {
44
45 template <class T>
46 struct is_config_value_type : std::false_type {};
47
48 #define CAF_ADD_CONFIG_VALUE_TYPE(type_name) \
49 template <> \
50 struct is_config_value_type<type_name> : std::true_type {}
51
52 CAF_ADD_CONFIG_VALUE_TYPE(none_t);
53 CAF_ADD_CONFIG_VALUE_TYPE(int64_t);
54 CAF_ADD_CONFIG_VALUE_TYPE(bool);
55 CAF_ADD_CONFIG_VALUE_TYPE(double);
56 CAF_ADD_CONFIG_VALUE_TYPE(timespan);
57 CAF_ADD_CONFIG_VALUE_TYPE(uri);
58 CAF_ADD_CONFIG_VALUE_TYPE(std::string);
59 CAF_ADD_CONFIG_VALUE_TYPE(std::vector<config_value>);
60 CAF_ADD_CONFIG_VALUE_TYPE(dictionary<config_value>);
61
62 #undef CAF_ADD_CONFIG_VALUE_TYPE
63
64 template <class T>
65 constexpr bool is_config_value_type_v = is_config_value_type<T>::value;
66
67 } // namespace caf::detail
68
69 namespace caf {
70
71 /// A type for config parameters with similar interface to a `variant`. This
72 /// type is not implemented as a simple variant alias because variants cannot
73 /// contain lists of themselves.
74 class CAF_CORE_EXPORT config_value {
75 public:
76 // -- member types -----------------------------------------------------------
77
78 using integer = int64_t;
79
80 using boolean = bool;
81
82 using real = double;
83
84 using string = std::string;
85
86 using list = std::vector<config_value>;
87
88 using dictionary = caf::dictionary<config_value>;
89
90 using types = detail::type_list<none_t, integer, boolean, real, timespan, uri,
91 string, list, dictionary>;
92
93 using variant_type = detail::tl_apply_t<types, variant>;
94
95 // -- constructors, destructors, and assignment operators --------------------
96
97 config_value() = default;
98
99 config_value(config_value&& other) = default;
100
101 config_value(const config_value& other) = default;
102
103 template <class T, class E = detail::enable_if_t<
104 !std::is_same<detail::decay_t<T>, config_value>::value>>
config_value(T && x)105 explicit config_value(T&& x) {
106 set(std::forward<T>(x));
107 }
108
109 config_value& operator=(config_value&& other) = default;
110
111 config_value& operator=(const config_value& other) = default;
112
113 template <class T, class E = detail::enable_if_t<
114 !std::is_same<detail::decay_t<T>, config_value>::value>>
operator =(T && x)115 config_value& operator=(T&& x) {
116 set(std::forward<T>(x));
117 return *this;
118 }
119
120 ~config_value();
121
122 // -- parsing ----------------------------------------------------------------
123
124 /// Tries to parse a value from given characters.
125 static expected<config_value> parse(string_view::iterator first,
126 string_view::iterator last);
127
128 /// Tries to parse a value from `str`.
129 static expected<config_value> parse(string_view str);
130
131 /// Tries to parse a config value (list) from `str` and to convert it to an
132 /// allowed input message type for `Handle`.
133 template <class Handle>
parse_msg(string_view str,const Handle &)134 static optional<message> parse_msg(string_view str, const Handle&) {
135 auto allowed = Handle::allowed_inputs();
136 return parse_msg_impl(str, allowed);
137 }
138
139 // -- properties -------------------------------------------------------------
140
141 /// Converts the value to a list with one element (unless the config value
142 /// holds `nullptr`). Does nothing if the value already is a list.
143 void convert_to_list();
144
145 /// Returns the value as a list, converting it to one if needed.
146 list& as_list();
147
148 /// Returns the value as a dictionary, converting it to one if needed. The
149 /// only data structure that CAF can convert to a dictionary is a list of
150 /// lists, where each nested list contains exactly two elements (key and
151 /// value). In all other cases, the conversion results in an empty dictionary.
152 dictionary& as_dictionary();
153
154 /// Appends `x` to a list. Converts this config value to a list first by
155 /// calling `convert_to_list` if needed.
156 void append(config_value x);
157
158 /// Returns a human-readable type name of the current value.
159 const char* type_name() const noexcept;
160
161 /// Returns the underlying variant.
get_data()162 variant_type& get_data() {
163 return data_;
164 }
165
166 /// Returns the underlying variant.
get_data() const167 const variant_type& get_data() const {
168 return data_;
169 }
170
171 /// Returns a pointer to the underlying variant.
get_data_ptr()172 variant_type* get_data_ptr() {
173 return &data_;
174 }
175
176 /// Returns a pointer to the underlying variant.
get_data_ptr() const177 const variant_type* get_data_ptr() const {
178 return &data_;
179 }
180
181 /// Checks whether this config value is not null.
operator bool() const182 explicit operator bool() const noexcept {
183 return data_.index() != 0;
184 }
185
186 /// Checks whether this config value is null.
operator !() const187 bool operator!() const noexcept {
188 return data_.index() == 0;
189 }
190
191 /// @private
192 ptrdiff_t signed_index() const noexcept;
193
194 // -- utility ----------------------------------------------------------------
195
196 /// @private
197 type_id_t type_id() const noexcept;
198
199 /// @private
200 error_code<sec> default_construct(type_id_t);
201
202 /// @private
203 expected<bool> to_boolean() const;
204
205 /// @private
206 expected<integer> to_integer() const;
207
208 /// @private
209 expected<real> to_real() const;
210
211 /// @private
212 expected<timespan> to_timespan() const;
213
214 /// @private
215 expected<uri> to_uri() const;
216
217 /// @private
218 expected<list> to_list() const;
219
220 /// @private
221 expected<dictionary> to_dictionary() const;
222
223 /// @private
224 bool can_convert_to_dictionary() const;
225
226 /// @private
227 template <class T, class Token>
convert_to(Token token) const228 expected<T> convert_to(Token token) const {
229 auto tmp = T{};
230 config_value_reader reader{this};
231 if (detail::load(reader, tmp, token))
232 return {std::move(tmp)};
233 else
234 return {reader.move_error()};
235 }
236
237 template <class T>
assign(const T & x)238 error assign(const T& x) {
239 if constexpr (detail::is_config_value_type_v<T>) {
240 data_ = x;
241 return {};
242 } else {
243 config_value_writer writer{this};
244 if (writer.apply(x))
245 return {};
246 else
247 return {writer.move_error()};
248 }
249 }
250
251 template <class T>
mapped_type_name()252 static constexpr string_view mapped_type_name() {
253 if constexpr (detail::is_complete<caf::type_name<T>>) {
254 return caf::type_name<T>::value;
255 } else if constexpr (detail::is_list_like_v<T>) {
256 return "list";
257 } else {
258 return "dictionary";
259 }
260 }
261
262 private:
263 // -- properties -------------------------------------------------------------
264
265 static const char* type_name_at_index(size_t index) noexcept;
266
267 static optional<message>
268 parse_msg_impl(string_view str, span<const type_id_list> allowed_types);
269
270 // -- auto conversion of related types ---------------------------------------
271
272 template <class T>
set_range(T & xs,std::true_type)273 void set_range(T& xs, std::true_type) {
274 auto& dict = as_dictionary();
275 dict.clear();
276 for (auto& [key, val] : xs)
277 dict.emplace(key, std::move(val));
278 }
279
280 template <class T>
set_range(T & xs,std::false_type)281 void set_range(T& xs, std::false_type) {
282 auto& ls = as_list();
283 ls.clear();
284 ls.insert(ls.end(), std::make_move_iterator(xs.begin()),
285 std::make_move_iterator(xs.end()));
286 }
287
288 template <class T>
set(T x)289 void set(T x) {
290 if constexpr (detail::is_config_value_type_v<T>) {
291 data_ = std::move(x);
292 } else if constexpr (std::is_integral<T>::value) {
293 data_ = static_cast<int64_t>(x);
294 } else if constexpr (std::is_convertible<T, const char*>::value) {
295 data_ = std::string{x};
296 } else {
297 static_assert(detail::is_iterable<T>::value);
298 using value_type = typename T::value_type;
299 detail::bool_token<detail::is_pair<value_type>::value> is_map_type;
300 set_range(x, is_map_type);
301 }
302 }
303
set(float x)304 void set(float x) {
305 data_ = static_cast<double>(x);
306 }
307
set(const char * x)308 void set(const char* x) {
309 data_ = std::string{x};
310 }
311
set(string_view x)312 void set(string_view x) {
313 data_ = std::string{x.begin(), x.end()};
314 }
315
316 // -- member variables -------------------------------------------------------
317
318 variant_type data_;
319 };
320
321 /// @relates config_value
322 CAF_CORE_EXPORT std::string to_string(const config_value& x);
323
324 // -- conversion via get_as ----------------------------------------------------
325
326 template <class T>
get_as(const config_value &,inspector_access_type::none)327 expected<T> get_as(const config_value&, inspector_access_type::none) {
328 static_assert(detail::always_false_v<T>,
329 "cannot convert to T: found no a suitable inspect overload");
330 }
331
332 template <class T>
get_as(const config_value &,inspector_access_type::unsafe)333 expected<T> get_as(const config_value&, inspector_access_type::unsafe) {
334 static_assert(detail::always_false_v<T>,
335 "cannot convert types that are tagged as unsafe");
336 }
337
338 template <class T>
339 expected<T>
get_as(const config_value & x,inspector_access_type::specialization token)340 get_as(const config_value& x, inspector_access_type::specialization token) {
341 return x.convert_to<T>(token);
342 }
343
344 template <class T>
345 expected<T>
get_as(const config_value & x,inspector_access_type::inspect token)346 get_as(const config_value& x, inspector_access_type::inspect token) {
347 return x.convert_to<T>(token);
348 }
349
350 template <class T>
351 expected<T>
get_as(const config_value & x,inspector_access_type::builtin_inspect token)352 get_as(const config_value& x, inspector_access_type::builtin_inspect token) {
353 return x.convert_to<T>(token);
354 }
355
356 template <class T>
get_as(const config_value & x,inspector_access_type::builtin)357 expected<T> get_as(const config_value& x, inspector_access_type::builtin) {
358 if constexpr (std::is_same<T, std::string>::value) {
359 return to_string(x);
360 } else if constexpr (std::is_same<T, bool>::value) {
361 return x.to_boolean();
362 } else if constexpr (std::is_integral<T>::value) {
363 if (auto result = x.to_integer()) {
364 if (detail::bounds_checker<T>::check(*result))
365 return static_cast<T>(*result);
366 else
367 return make_error(sec::conversion_failed, "narrowing error");
368 } else {
369 return std::move(result.error());
370 }
371 } else if constexpr (std::is_floating_point<T>::value) {
372 if (auto result = x.to_real()) {
373 if constexpr (sizeof(T) >= sizeof(config_value::real)) {
374 return *result;
375 } else {
376 auto narrowed = static_cast<T>(*result);
377 if (!std::isfinite(*result) || std::isfinite(narrowed)) {
378 return narrowed;
379 } else {
380 return make_error(sec::conversion_failed, "narrowing error");
381 }
382 }
383 } else {
384 return std::move(result.error());
385 }
386 } else {
387 static_assert(detail::always_false_v<T>,
388 "sorry, this conversion is not implemented yet");
389 }
390 }
391
392 template <class T>
get_as(const config_value & x,inspector_access_type::empty)393 expected<T> get_as(const config_value& x, inspector_access_type::empty) {
394 // Technically, we could always simply return T{} here. However,
395 // *semantically* it only makes sense to converts dictionaries to objects. So
396 // at least we check for this condition here.
397 if (x.can_convert_to_dictionary())
398 return T{};
399 else
400 return make_error(sec::conversion_failed,
401 "invalid element type: expected a dictionary");
402 }
403
404 template <class T, size_t... Is>
405 expected<T>
get_as_tuple(const config_value::list & x,std::index_sequence<Is...>)406 get_as_tuple(const config_value::list& x, std::index_sequence<Is...>) {
407 auto boxed = std::make_tuple(get_as<std::tuple_element_t<Is, T>>(x[Is])...);
408 if ((get<Is>(boxed) && ...))
409 return T{std::move(*get<Is>(boxed))...};
410 else
411 return make_error(sec::conversion_failed, "invalid element types");
412 }
413
414 template <class T>
get_as(const config_value & x,inspector_access_type::tuple)415 expected<T> get_as(const config_value& x, inspector_access_type::tuple) {
416 static_assert(!std::is_array<T>::value,
417 "cannot return an array from a function");
418 if (auto wrapped_values = x.to_list()) {
419 static constexpr size_t n = std::tuple_size<T>::value;
420 if (wrapped_values->size() == n)
421 return get_as_tuple<T>(*wrapped_values, std::make_index_sequence<n>{});
422 else
423 return make_error(sec::conversion_failed, "wrong number of arguments");
424 } else {
425 return {std::move(wrapped_values.error())};
426 }
427 }
428
429 template <class T>
get_as(const config_value & x,inspector_access_type::map)430 expected<T> get_as(const config_value& x, inspector_access_type::map) {
431 using key_type = typename T::key_type;
432 using mapped_type = typename T::mapped_type;
433 T result;
434 if (auto dict = x.to_dictionary()) {
435 for (auto&& [string_key, wrapped_value] : *dict) {
436 config_value wrapped_key{std::move(string_key)};
437 if (auto key = get_as<key_type>(wrapped_key)) {
438 if (auto val = get_as<mapped_type>(wrapped_value)) {
439 if (!result.emplace(std::move(*key), std::move(*val)).second) {
440 return make_error(sec::conversion_failed,
441 "ambiguous mapping of keys to key_type");
442 }
443 } else {
444 return make_error(sec::conversion_failed,
445 "failed to convert values to mapped_type");
446 }
447 } else {
448 return make_error(sec::conversion_failed,
449 "failed to convert keys to key_type");
450 }
451 }
452 return {std::move(result)};
453 } else {
454 return {std::move(dict.error())};
455 }
456 }
457
458 template <class T>
get_as(const config_value & x,inspector_access_type::list)459 expected<T> get_as(const config_value& x, inspector_access_type::list) {
460 if (auto wrapped_values = x.to_list()) {
461 using value_type = typename T::value_type;
462 T result;
463 if constexpr (detail::has_reserve_v<T>)
464 result.reserve(wrapped_values->size());
465 for (const auto& wrapped_value : *wrapped_values)
466 if (auto maybe_value = get_as<value_type>(wrapped_value)) {
467 if constexpr (detail::has_emplace_back_v<T>)
468 result.emplace_back(std::move(*maybe_value));
469 else
470 result.insert(result.end(), std::move(*maybe_value));
471 } else {
472 return {std::move(maybe_value.error())};
473 }
474 return {std::move(result)};
475 } else {
476 return {std::move(wrapped_values.error())};
477 }
478 }
479
480 /// Converts a @ref config_value to builtin types or user-defined types that
481 /// opted into the type inspection API.
482 /// @relates config_value
483 template <class T>
get_as(const config_value & value)484 expected<T> get_as(const config_value& value) {
485 if constexpr (std::is_same<T, timespan>::value) {
486 return value.to_timespan();
487 } else if constexpr (std::is_same<T, config_value::list>::value) {
488 return value.to_list();
489 } else if constexpr (std::is_same<T, config_value::dictionary>::value) {
490 return value.to_dictionary();
491 } else if constexpr (std::is_same<T, uri>::value) {
492 return value.to_uri();
493 } else {
494 auto token = inspect_access_type<config_value_reader, T>();
495 return get_as<T>(value, token);
496 }
497 }
498
499 // -- conversion via get_or ----------------------------------------------------
500
501 /// Customization point for configuring automatic mappings from default value
502 /// types to deduced types. For example, `get_or(value, "foo"sv)` must return a
503 /// `string` rather than a `string_view`. However, user-defined overloads *must
504 /// not* specialize this class for any type from the namespaces `std` or `caf`.
505 template <class T>
506 struct get_or_deduction_guide {
507 using value_type = T;
508 template <class V>
convertcaf::get_or_deduction_guide509 static decltype(auto) convert(V&& x) {
510 return std::forward<V>(x);
511 }
512 };
513
514 template <>
515 struct get_or_deduction_guide<string_view> {
516 using value_type = std::string;
convertcaf::get_or_deduction_guide517 static value_type convert(string_view str) {
518 return {str.begin(), str.end()};
519 }
520 };
521
522 template <>
523 struct get_or_deduction_guide<const char*> {
524 using value_type = std::string;
convertcaf::get_or_deduction_guide525 static value_type convert(const char* str) {
526 return {str};
527 }
528 };
529
530 template <class T>
531 struct get_or_deduction_guide<span<T>> {
532 using value_type = std::vector<T>;
convertcaf::get_or_deduction_guide533 static value_type convert(span<T> buf) {
534 return {buf.begin(), buf.end()};
535 }
536 };
537
538 /// Configures @ref get_or to uses the @ref get_or_deduction_guide.
539 struct get_or_auto_deduce {};
540
541 /// Converts a @ref config_value to `To` or returns `fallback` if the conversion
542 /// fails.
543 /// @relates config_value
544 template <class To = get_or_auto_deduce, class Fallback>
get_or(const config_value & x,Fallback && fallback)545 auto get_or(const config_value& x, Fallback&& fallback) {
546 if constexpr (std::is_same<To, get_or_auto_deduce>::value) {
547 using guide = get_or_deduction_guide<std::decay_t<Fallback>>;
548 using value_type = typename guide::value_type;
549 if (auto val = get_as<value_type>(x))
550 return std::move(*val);
551 else
552 return guide::convert(std::forward<Fallback>(fallback));
553 } else {
554 if (auto val = get_as<To>(x))
555 return std::move(*val);
556 else
557 return To{std::forward<Fallback>(fallback)};
558 }
559 }
560
561 // -- SumType-like access ------------------------------------------------------
562
563 template <class T>
564 [[deprecated("use get_as or get_or instead")]] optional<T>
legacy_get_if(const config_value * x)565 legacy_get_if(const config_value* x) {
566 if (auto val = get_as<T>(*x))
567 return {std::move(*val)};
568 else
569 return {};
570 }
571
572 template <class T>
get_if(const config_value * x)573 auto get_if(const config_value* x) {
574 if constexpr (detail::is_config_value_type_v<T>) {
575 return get_if<T>(x->get_data_ptr());
576 } else {
577 return legacy_get_if<T>(x);
578 }
579 }
580
581 template <class T>
get_if(config_value * x)582 auto get_if(config_value* x) {
583 if constexpr (detail::is_config_value_type_v<T>) {
584 return get_if<T>(x->get_data_ptr());
585 } else {
586 return legacy_get_if<T>(x);
587 }
588 }
589
590 template <class T>
591 [[deprecated("use get_as or get_or instead")]] T
legacy_get(const config_value & x)592 legacy_get(const config_value& x) {
593 auto val = get_as<T>(x);
594 if (!val)
595 CAF_RAISE_ERROR("legacy_get: conversion failed");
596 return std::move(*val);
597 }
598
599 template <class T>
get(const config_value & x)600 decltype(auto) get(const config_value& x) {
601 if constexpr (detail::is_config_value_type_v<T>) {
602 return get<T>(x.get_data());
603 } else {
604 return legacy_get<T>(x);
605 }
606 }
607
608 template <class T>
get(config_value & x)609 decltype(auto) get(config_value& x) {
610 if constexpr (detail::is_config_value_type_v<T>) {
611 return get<T>(x.get_data());
612 } else {
613 return legacy_get<T>(x);
614 }
615 }
616
617 template <class T>
618 [[deprecated("use get_as or get_or instead")]] bool
legacy_holds_alternative(const config_value & x)619 legacy_holds_alternative(const config_value& x) {
620 if (auto val = get_as<T>(x))
621 return true;
622 else
623 return false;
624 }
625
626 template <class T>
holds_alternative(const config_value & x)627 auto holds_alternative(const config_value& x) {
628 if constexpr (detail::is_config_value_type_v<T>) {
629 return holds_alternative<T>(x.get_data());
630 } else {
631 return legacy_holds_alternative<T>(x);
632 }
633 }
634
635 // -- comparison operator overloads --------------------------------------------
636
637 /// @relates config_value
638 CAF_CORE_EXPORT bool operator<(const config_value& x, const config_value& y);
639
640 /// @relates config_value
641 CAF_CORE_EXPORT bool operator<=(const config_value& x, const config_value& y);
642
643 /// @relates config_value
644 CAF_CORE_EXPORT bool operator==(const config_value& x, const config_value& y);
645
646 /// @relates config_value
647 CAF_CORE_EXPORT bool operator>(const config_value& x, const config_value& y);
648
649 /// @relates config_value
650 CAF_CORE_EXPORT bool operator>=(const config_value& x, const config_value& y);
651
652 /// @relates config_value
operator !=(const config_value & x,const config_value & y)653 inline bool operator!=(const config_value& x, const config_value& y) {
654 return !(x == y);
655 }
656
657 /// @relates config_value
658 CAF_CORE_EXPORT std::ostream& operator<<(std::ostream& out,
659 const config_value& x);
660
661 // -- convenience APIs ---------------------------------------------------------
662
663 template <class... Ts>
make_config_value_list(Ts &&...xs)664 config_value make_config_value_list(Ts&&... xs) {
665 std::vector<config_value> lst{config_value{std::forward<Ts>(xs)}...};
666 return config_value{std::move(lst)};
667 }
668
669 // -- inspection API -----------------------------------------------------------
670
671 template <>
672 struct variant_inspector_traits<config_value> {
673 using value_type = config_value;
674
675 static constexpr type_id_t allowed_types[] = {
676 type_id_v<none_t>,
677 type_id_v<config_value::integer>,
678 type_id_v<config_value::boolean>,
679 type_id_v<config_value::real>,
680 type_id_v<timespan>,
681 type_id_v<uri>,
682 type_id_v<config_value::string>,
683 type_id_v<config_value::list>,
684 type_id_v<config_value::dictionary>,
685 };
686
type_indexcaf::variant_inspector_traits687 static auto type_index(const config_value& x) {
688 return x.get_data().index();
689 }
690
691 template <class F, class Value>
visitcaf::variant_inspector_traits692 static auto visit(F&& f, Value&& x) {
693 return caf::visit(std::forward<F>(f), x.get_data());
694 }
695
696 template <class U>
assigncaf::variant_inspector_traits697 static void assign(value_type& x, U&& value) {
698 x = std::move(value);
699 }
700
701 template <class F>
loadcaf::variant_inspector_traits702 static bool load(type_id_t type, F continuation) {
703 switch (type) {
704 default:
705 return false;
706 case type_id_v<none_t>: {
707 auto tmp = config_value{};
708 continuation(tmp);
709 return true;
710 }
711 case type_id_v<config_value::integer>: {
712 auto tmp = config_value::integer{};
713 continuation(tmp);
714 return true;
715 }
716 case type_id_v<config_value::boolean>: {
717 auto tmp = config_value::boolean{};
718 continuation(tmp);
719 return true;
720 }
721 case type_id_v<config_value::real>: {
722 auto tmp = config_value::real{};
723 continuation(tmp);
724 return true;
725 }
726 case type_id_v<timespan>: {
727 auto tmp = timespan{};
728 continuation(tmp);
729 return true;
730 }
731 case type_id_v<uri>: {
732 auto tmp = uri{};
733 continuation(tmp);
734 return true;
735 }
736 case type_id_v<config_value::string>: {
737 auto tmp = config_value::string{};
738 continuation(tmp);
739 return true;
740 }
741 case type_id_v<config_value::list>: {
742 auto tmp = config_value::list{};
743 continuation(tmp);
744 return true;
745 }
746 case type_id_v<config_value::dictionary>: {
747 auto tmp = config_value::dictionary{};
748 continuation(tmp);
749 return true;
750 }
751 }
752 }
753 };
754
755 template <>
756 struct inspector_access<config_value> : variant_inspector_access<config_value> {
757 // nop
758 };
759
760 } // namespace caf
761