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