Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #ifndef ELEKTRA_KDBVALUE_HPP
10 : #define ELEKTRA_KDBVALUE_HPP
11 :
12 : #include <kdbmacros.h>
13 :
14 : #ifdef HAVE_KDBCONFIG_H
15 : #include <kdbconfig.h>
16 : #else
17 : #define DEBUG 0
18 : #define VERBOSE 0
19 : #endif
20 :
21 : #include <algorithm>
22 : #include <cassert>
23 : #include <fstream>
24 : #include <functional>
25 : #include <iostream>
26 : #include <kdbmeta.h>
27 : #include <map>
28 : #include <memory>
29 : #include <set>
30 : #include <stdexcept>
31 : #include <string>
32 : #include <unordered_map>
33 : #include <vector>
34 :
35 : #include <kdbproposal.h>
36 : #include <keyset.hpp>
37 :
38 : // #include <kdbprivate.h> // for debugging (to see values of internal structures)
39 :
40 : namespace kdb
41 : {
42 :
43 :
44 : // some widely used interfaces
45 :
46 : /**
47 : * @brief This type is being used as bottom type that always fails.
48 : */
49 : class none_t
50 : {
51 : };
52 :
53 : template <>
54 : inline void Key::set (none_t)
55 : {
56 : }
57 :
58 : template <>
59 : inline none_t Key::get () const
60 : {
61 : none_t ret;
62 : return ret;
63 : }
64 :
65 :
66 : /**
67 : * @brief Base class for all layers.
68 : */
69 : class Layer
70 : {
71 : public:
72 : virtual ~Layer (){};
73 : virtual std::string id () const = 0;
74 : virtual std::string operator() () const = 0;
75 : };
76 :
77 : /**
78 : * @brief Everything implementing this interface can be used as layer
79 : *
80 : * Different from "Layer" objects they will not be constructed on activation
81 : * but instead only the WrapLayer will be constructed and the wrapped object
82 : * will be passed along by reference.
83 : *
84 : * @note the lifetime must be beyond layer deactivation!
85 : */
86 6 : class Wrapped
87 : {
88 : public:
89 0 : virtual ~Wrapped (){};
90 : virtual std::string layerId () const = 0;
91 : virtual std::string layerVal () const = 0;
92 : };
93 :
94 : class WrapLayer : public Layer
95 : {
96 : public:
97 40 : explicit WrapLayer (Wrapped const & wrapped) : m_wrapped (wrapped)
98 : {
99 : }
100 :
101 38 : virtual ~WrapLayer (){};
102 :
103 152 : virtual std::string id () const
104 : {
105 152 : return m_wrapped.layerId ();
106 : }
107 :
108 96 : virtual std::string operator() () const
109 : {
110 96 : return m_wrapped.layerVal ();
111 : }
112 :
113 : private:
114 : Wrapped const & m_wrapped;
115 : };
116 :
117 : class KeyValueLayer : public kdb::Layer
118 : {
119 : public:
120 204 : KeyValueLayer (std::string key, std::string value) : m_key (std::move (key)), m_value (std::move (value))
121 : {
122 68 : }
123 :
124 162 : virtual ~KeyValueLayer (){};
125 :
126 200 : std::string id () const override
127 : {
128 400 : return m_key;
129 : }
130 300 : std::string operator() () const override
131 : {
132 600 : return m_value;
133 : }
134 :
135 : private:
136 : std::string m_key;
137 : std::string m_value;
138 : };
139 :
140 : /**
141 : * @brief Base class for values to be observed.
142 : *
143 : * updateContext() is called whenever a context tells a value that it
144 : * should reevaluate its name and update its cache.
145 : */
146 6 : class ValueObserver
147 : {
148 : public:
149 : virtual ~ValueObserver () = 0;
150 : virtual void updateContext (bool write = true) const = 0;
151 : virtual kdb::Key getDepKey () const = 0;
152 :
153 : typedef std::reference_wrapper<ValueObserver> reference;
154 : };
155 :
156 : /**
157 : * @brief Needed to put a ValueObserver in a map
158 : *
159 : * @return Comparison result
160 : */
161 : inline bool operator< (ValueObserver const & lhs, ValueObserver const & rhs)
162 : {
163 200 : return &lhs < &rhs;
164 : }
165 :
166 0 : inline ValueObserver::~ValueObserver ()
167 : {
168 : }
169 :
170 :
171 6 : class ValueSubject
172 : {
173 : public:
174 : virtual void notifyInThread () = 0;
175 : };
176 :
177 : /**
178 : * @brief Used by contexts for callbacks (to run code using a mutex).
179 : *
180 : * Following scenarios are possible:
181 : * !oldName && !newName: execute code, do nothing else
182 : * !oldName && newName: attach
183 : * oldName && newName: reattach
184 : * oldName == newName: assignment, attach for inter-thread updates
185 : * oldName && !newName: detach
186 : */
187 2874 : struct Command
188 : {
189 : public:
190 : typedef std::pair<std::string, std::string> Pair;
191 : /**
192 : * @brief Typedef for function that returns oldKey, newKey pair
193 : */
194 : typedef std::function<Pair ()> Func;
195 : Command (ValueSubject const & v_, Func & execute_, bool hasChanged_ = false)
196 2874 : : v (const_cast<ValueSubject &> (v_)), execute (execute_), hasChanged (hasChanged_), oldKey (), newKey ()
197 : {
198 : }
199 :
200 : Pair operator() ()
201 : {
202 1916 : return execute ();
203 : }
204 :
205 : ValueSubject & v; // this pointer
206 : Func & execute; // to be executed within lock
207 : bool hasChanged; // if the value (m_cache) has changed and value propagation is needed
208 : std::string oldKey; // old name before assignment
209 : std::string newKey; // new name after assignment
210 : };
211 :
212 : // Default Policies for Value
213 :
214 : class NoContext
215 : {
216 : public:
217 : /**
218 : * @brief attach a new value
219 : *
220 : * NoContext will never update anything
221 : */
222 : void attachByName (ELEKTRA_UNUSED std::string const & key_name, ELEKTRA_UNUSED ValueObserver & ValueObserver)
223 : {
224 : }
225 :
226 : /**
227 : * @brief The evaluated equals the non-evaluated name!
228 : *
229 : * @return NoContext always returns the same string
230 : */
231 : std::string evaluate (std::string const & key_name) const
232 : {
233 8 : return key_name;
234 : }
235 :
236 : std::string evaluate (std::string const & key_name,
237 : std::function<bool(std::string const &, std::string &, bool in_group)> const &) const
238 : {
239 0 : return key_name;
240 : }
241 :
242 : /**
243 : * @brief (Re)attaches a ValueSubject to a thread or simply
244 : * execute code in a locked section.
245 : *
246 : * NoContext just executes the function and does not
247 : * attach/reattach/detach
248 : *
249 : * @param c the command to apply
250 : */
251 58 : void execute (Command & c)
252 : {
253 116 : c ();
254 58 : }
255 : };
256 :
257 : /**
258 : * @brief Implements lookup with spec.
259 : */
260 : class DefaultGetPolicy
261 : {
262 : public:
263 544 : static Key get (KeySet & ks, Key const & spec)
264 : {
265 544 : return ks.lookup (spec, ckdb::KDB_O_SPEC | ckdb::KDB_O_CREATE);
266 : }
267 : };
268 :
269 : /**
270 : * @brief Implements creating user/ key when key is not found.
271 : */
272 : class DefaultSetPolicy
273 : {
274 : public:
275 94 : static Key set (KeySet & ks, Key const & spec)
276 : {
277 376 : return setWithNamespace (ks, spec, "user");
278 : }
279 :
280 96 : static Key setWithNamespace (KeySet & ks, Key const & spec, std::string const & ns)
281 : {
282 192 : std::string const & name = spec.getName ();
283 :
284 288 : kdb::Key k (ns + "/" + name, KEY_END);
285 96 : ks.append (k);
286 :
287 96 : return k;
288 : }
289 : };
290 :
291 : class DefaultWritePolicy
292 : {
293 : public:
294 : static const bool allowed = true;
295 : };
296 :
297 : class ReadOnlyPolicy
298 : {
299 : public:
300 : static const bool allowed = false;
301 : };
302 :
303 : class DefaultObserverPolicy
304 : {
305 : public:
306 : typedef double type;
307 : };
308 :
309 : class NoLockPolicy
310 : {
311 : public:
312 : void lock ()
313 : {
314 : }
315 : void unlock ()
316 : {
317 : }
318 : };
319 :
320 : /*
321 : * This technique with the PolicySelector and Discriminator is taken
322 : * from the book "C++ Templates - The Complete Guide"
323 : * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
324 : * in Chapter 16 Templates and Inheritance: Named Template Arguments
325 : *
326 : * The technique allows users of the class Value to use any number
327 : * and order of policies as desired.
328 : */
329 :
330 :
331 : template <typename Base, int D>
332 : class Discriminator : public Base
333 : {
334 : };
335 :
336 : template <typename Setter1, typename Setter2, typename Setter3, typename Setter4, typename Setter5, typename Setter6>
337 : class PolicySelector : public Discriminator<Setter1, 1>,
338 : public Discriminator<Setter2, 2>,
339 : public Discriminator<Setter3, 3>,
340 : public Discriminator<Setter4, 4>,
341 : public Discriminator<Setter5, 5>,
342 : public Discriminator<Setter6, 6>
343 : {
344 : };
345 :
346 : class DefaultPolicies
347 : {
348 : public:
349 : typedef DefaultGetPolicy GetPolicy;
350 : typedef DefaultSetPolicy SetPolicy;
351 : typedef NoContext ContextPolicy;
352 : typedef DefaultWritePolicy WritePolicy;
353 : typedef DefaultObserverPolicy ObserverPolicy;
354 : typedef NoLockPolicy LockPolicy;
355 : };
356 :
357 : class DefaultPolicyArgs : virtual public DefaultPolicies
358 : {
359 : };
360 :
361 :
362 : // class templates to override the default policy values
363 :
364 : /// Needed by the user to set one of the policies
365 : ///
366 : /// @tparam Policy
367 : template <typename Policy>
368 : class GetPolicyIs : virtual public DefaultPolicies
369 : {
370 : public:
371 : typedef Policy GetPolicy;
372 : };
373 :
374 :
375 : /// Needed by the user to set one of the policies
376 : ///
377 : /// @tparam Policy
378 : template <typename Policy>
379 : class SetPolicyIs : virtual public DefaultPolicies
380 : {
381 : public:
382 : typedef Policy SetPolicy;
383 : };
384 :
385 :
386 : /// Needed by the user to set one of the policies
387 : ///
388 : /// @tparam Policy
389 : template <typename Policy>
390 : class ContextPolicyIs : virtual public DefaultPolicies
391 : {
392 : public:
393 : typedef Policy ContextPolicy;
394 : };
395 :
396 :
397 : /// Needed by the user to set one of the policies
398 : ///
399 : /// @tparam Policy
400 : template <typename Policy>
401 : class WritePolicyIs : virtual public DefaultPolicies
402 : {
403 : public:
404 : typedef Policy WritePolicy;
405 : };
406 :
407 :
408 : /// Needed by the user to set one of the policies
409 : ///
410 : /// @tparam Policy
411 : template <typename Policy>
412 : class ObserverPolicyIs : virtual public DefaultPolicies
413 : {
414 : public:
415 : typedef Policy ObserverPolicy;
416 : };
417 :
418 :
419 : /// Needed by the user to set one of the policies
420 : ///
421 : /// @tparam Policy
422 : template <typename Policy>
423 : class LockPolicyIs : virtual public DefaultPolicies
424 : {
425 : public:
426 : typedef Policy LockPolicy;
427 : };
428 :
429 :
430 : // standard types
431 :
432 : template <typename T, typename PolicySetter1 = DefaultPolicyArgs, typename PolicySetter2 = DefaultPolicyArgs,
433 : typename PolicySetter3 = DefaultPolicyArgs, typename PolicySetter4 = DefaultPolicyArgs,
434 : typename PolicySetter5 = DefaultPolicyArgs, typename PolicySetter6 = DefaultPolicyArgs>
435 : class Value : public ValueObserver, public ValueSubject, public Wrapped
436 : {
437 : public:
438 : typedef T type;
439 :
440 : typedef PolicySelector<PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4, PolicySetter5, PolicySetter6> Policies;
441 :
442 36 : Value (const Value &) = default;
443 :
444 : // not to be constructed yourself
445 182 : Value<T, PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4, PolicySetter5, PolicySetter6> (
446 : KeySet & ks, typename Policies::ContextPolicy & context_, kdb::Key spec)
447 596 : : m_cache (), m_hasChanged (false), m_ks (ks), m_context (context_), m_spec (spec)
448 : {
449 : assert (m_spec.getName ()[0] == '/' && "spec keys are not yet supported");
450 364 : m_context.attachByName (m_spec.getName (), *this);
451 908 : Command::Func fun = [this]() -> Command::Pair {
452 920 : this->unsafeUpdateKeyUsingContext (m_context.evaluate (m_spec.getName ()));
453 182 : this->unsafeSyncCache (); // set m_cache
454 728 : return std::make_pair ("", m_key.getName ());
455 546 : };
456 546 : Command command (*this, fun);
457 182 : m_context.execute (command);
458 182 : }
459 :
460 188 : ~Value<T, PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4, PolicySetter5, PolicySetter6> ()
461 : {
462 376 : Command::Func fun = [this]() -> Command::Pair {
463 376 : std::string oldName = m_key.getName ();
464 376 : m_key = static_cast<ckdb::Key *> (nullptr);
465 : // after destructor we do not need to care about
466 : // invariant anymore. But we need to care about
467 : // thread safe m_key.
468 564 : return std::make_pair (oldName, "");
469 564 : };
470 564 : Command command (*this, fun);
471 188 : m_context.execute (command);
472 802 : }
473 :
474 : typedef Value<T, PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4, PolicySetter5, PolicySetter6> V;
475 :
476 : V const & operator= (type n)
477 : {
478 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
479 174 : m_cache = n;
480 148 : m_hasChanged = true;
481 148 : syncKeySet ();
482 :
483 : return *this;
484 : }
485 :
486 : type operator++ ()
487 : {
488 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
489 : type ret = ++m_cache;
490 : m_hasChanged = true;
491 : syncKeySet ();
492 : return ret;
493 : }
494 :
495 : type operator++ (int)
496 : {
497 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
498 : type ret = m_cache++;
499 : m_hasChanged = true;
500 : syncKeySet ();
501 : return ret;
502 : }
503 :
504 : type operator-- ()
505 : {
506 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
507 2 : type ret = --m_cache;
508 2 : m_hasChanged = true;
509 2 : syncKeySet ();
510 : return ret;
511 : }
512 :
513 : type operator-- (int)
514 : {
515 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
516 : type ret = m_cache--;
517 : m_hasChanged = true;
518 : syncKeySet ();
519 : return ret;
520 : }
521 :
522 : V & operator= (V const & rhs)
523 : {
524 : static_assert (Policies::WritePolicy::allowed, "read only contextual value");
525 : if (this != &rhs)
526 : {
527 2 : m_cache = rhs;
528 2 : m_hasChanged = true;
529 2 : syncKeySet ();
530 : }
531 : return *this;
532 : }
533 :
534 : #define ELEKTRA_DEFINE_OPERATOR(op) \
535 : V & operator op (type const & rhs) \
536 : { \
537 : static_assert (Policies::WritePolicy::allowed, "read only contextual value"); \
538 : m_cache op rhs; \
539 : m_hasChanged = true; \
540 : syncKeySet (); \
541 : return *this; \
542 : }
543 :
544 2 : ELEKTRA_DEFINE_OPERATOR (-=)
545 6 : ELEKTRA_DEFINE_OPERATOR (+=)
546 2 : ELEKTRA_DEFINE_OPERATOR (*=)
547 2 : ELEKTRA_DEFINE_OPERATOR (/=)
548 2 : ELEKTRA_DEFINE_OPERATOR (%=)
549 : ELEKTRA_DEFINE_OPERATOR (^=)
550 : ELEKTRA_DEFINE_OPERATOR (&=)
551 2 : ELEKTRA_DEFINE_OPERATOR (|=)
552 :
553 : #undef ELEKTRA_DEFINE_OPERATOR
554 :
555 : type operator- () const
556 : {
557 4 : return -m_cache;
558 : }
559 :
560 : type operator~ () const
561 : {
562 6 : return ~m_cache;
563 : }
564 :
565 : type operator! () const
566 : {
567 6 : return !m_cache;
568 : }
569 :
570 : // type conversion
571 : operator type () const
572 : {
573 194 : return m_cache;
574 : }
575 :
576 : /**
577 : * @return the context bound to the value
578 : */
579 : typename Policies::ContextPolicy & context () const
580 : {
581 : /// We allow manipulation of context for const
582 : /// objects
583 : return const_cast<typename Policies::ContextPolicy &> (m_context);
584 : }
585 :
586 : /**
587 : * @brief Shortcut for context()
588 : *
589 : * @see context()
590 : */
591 : typename Policies::ContextPolicy & c () const
592 : {
593 : return context ();
594 : }
595 :
596 : /**
597 : * @return Specification Key
598 : */
599 : Key const & getSpec () const
600 : {
601 8 : return m_spec;
602 : }
603 :
604 : /**
605 : * @brief Returns the current name of contextual value
606 : *
607 : * @return name under contextual interpretation
608 : */
609 : std::string getName () const
610 : {
611 276 : return m_key.getName ();
612 : }
613 :
614 198 : std::string layerId () const override
615 : {
616 1188 : const Key meta = m_spec.getMeta<const Key> ("layer/name");
617 198 : if (meta) return meta.getString ();
618 168 : return m_spec.getBaseName ();
619 : }
620 :
621 92 : std::string layerVal () const override
622 : {
623 92 : return m_key.getString ();
624 : }
625 :
626 :
627 : /**
628 : * @brief Sync key(set) to cache
629 : */
630 4 : void syncCache () const
631 : {
632 16 : Command::Func fun = [this]() -> Command::Pair {
633 8 : std::string const & oldKey = m_key.getName ();
634 4 : this->unsafeLookupKey ();
635 4 : this->unsafeSyncCache ();
636 16 : return std::make_pair (oldKey, m_key.getName ());
637 12 : };
638 12 : Command command (*this, fun);
639 4 : m_context.execute (command);
640 4 : }
641 :
642 : /**
643 : * @brief Sync cache to key(set)
644 : */
645 184 : void syncKeySet () const
646 : {
647 552 : Command::Func fun = [this]() -> Command::Pair {
648 368 : std::string const & oldKey = m_key.getName ();
649 184 : this->unsafeSyncKeySet ();
650 736 : return std::make_pair (oldKey, m_key.getName ());
651 552 : };
652 552 : Command command (*this, fun, m_hasChanged);
653 184 : m_context.execute (command);
654 184 : }
655 :
656 : private:
657 556 : void unsafeUpdateKeyUsingContext (std::string const & evaluatedName) const
658 : {
659 2224 : Key spec (m_spec.dup ());
660 556 : spec.setName (evaluatedName);
661 1680 : m_key = Policies::GetPolicy::get (m_ks, spec);
662 : assert (m_key);
663 556 : }
664 :
665 4 : void unsafeLookupKey () const
666 : {
667 : // Key spec (m_spec.dup ());
668 : // spec.setName (m_context.evaluate(m_spec.getName()));
669 : // m_key = Policies::GetPolicy::get (m_ks, spec);
670 12 : m_key = Policies::GetPolicy::get (m_ks, m_key);
671 : assert (m_key);
672 4 : }
673 :
674 : /**
675 : * @brief Unsafe: Execute this method *only* in a Command execution
676 : */
677 104 : void unsafeSyncCache () const
678 : {
679 : assert (m_key);
680 :
681 : #if DEBUG && VERBOSE
682 : std::cout << "will get name: " << m_key.getName () << " value: " << m_key.getString () << std::endl;
683 : #endif
684 :
685 798 : m_cache = m_key.get<type> ();
686 104 : }
687 :
688 : /**
689 : * @brief Execute this method *only* in a Command execution
690 : */
691 510 : void unsafeSyncKeySet () const
692 : {
693 938 : if (m_hasChanged && m_key.getName ().at (0) == '/')
694 : {
695 96 : m_hasChanged = false;
696 384 : Key spec (m_spec.dup ());
697 192 : spec.setName (m_key.getName ());
698 288 : m_key = Policies::SetPolicy::set (m_ks, spec);
699 : }
700 : assert (m_key);
701 586 : m_key.set<type> (m_cache);
702 :
703 : #if DEBUG && VERBOSE
704 : std::cout << "set name: " << m_key.getName () << " value: " << m_key.getString () << std::endl;
705 : #endif
706 510 : }
707 :
708 : /**
709 : * @brief Update to new value because of assignment
710 : */
711 30 : void notifyInThread () override
712 : {
713 30 : unsafeSyncCache (); // always called from save context
714 30 : }
715 :
716 :
717 398 : virtual void updateContext (bool write) const override
718 : {
719 1194 : std::string evaluatedName = m_context.evaluate (m_spec.getName ());
720 : #if DEBUG && VERBOSE
721 : std::cout << "update context " << evaluatedName << " from " << m_spec.getName () << " with write " << write << std::endl;
722 : #endif
723 :
724 2618 : Command::Func fun = [this, &evaluatedName, write]() -> Command::Pair {
725 796 : std::string oldKey = m_key.getName ();
726 748 : if (write && evaluatedName == oldKey)
727 : {
728 : // nothing changed, same name
729 : return std::make_pair (evaluatedName, evaluatedName);
730 : }
731 :
732 374 : if (write)
733 : {
734 326 : this->unsafeSyncKeySet (); // flush out what currently is in cache
735 : }
736 :
737 374 : this->unsafeUpdateKeyUsingContext (evaluatedName);
738 374 : this->unsafeSyncCache (); // read what we have under new context
739 :
740 1122 : return std::make_pair (oldKey, m_key.getName ());
741 796 : };
742 1194 : Command command (*this, fun);
743 398 : m_context.execute (command);
744 398 : }
745 :
746 60 : virtual kdb::Key getDepKey () const override
747 : {
748 180 : kdb::Key dep ("/" + layerId (), KEY_END);
749 : // rename to /layer/order
750 360 : const Key meta = m_spec.getMeta<const Key> ("layer/order");
751 60 : if (meta)
752 : {
753 20 : dep.setMeta ("order", meta.getString ());
754 : }
755 344 : m_context.evaluate (m_spec.getName (), [&](std::string const & current_id, std::string &, bool) {
756 : #if DEBUG && VERBOSE
757 : std::cout << "add dep " << current_id << " to " << dep.getName () << std::endl;
758 : #endif
759 176 : ckdb::elektraMetaArrayAdd (*dep, "dep", ("/" + current_id).c_str ());
760 44 : return false;
761 : });
762 60 : return dep;
763 : }
764 :
765 : private:
766 : /**
767 : * @brief A transient mutable cache for very fast read-access.
768 : */
769 : mutable type m_cache;
770 :
771 : /**
772 : * @brief triggers transition from transient to persistent keys
773 : * @retval true if m_cache was changed
774 : */
775 : mutable bool m_hasChanged;
776 :
777 : /**
778 : * @brief Reference to the keyset in use
779 : *
780 : * only accessed using
781 : * Command, that might be multi-thread safe depending on
782 : * ContextPolicyIs
783 : */
784 : KeySet & m_ks;
785 :
786 : /**
787 : * @brief The context that might be
788 : *
789 : * - thread safe
790 : * - allow COP
791 : */
792 : typename Policies::ContextPolicy & m_context;
793 :
794 : /**
795 : * @brief The specification key
796 : *
797 : * Is only read and will not be changed.
798 : *
799 : * Might start with / or with spec/ (not implemented yet)
800 : */
801 : Key m_spec;
802 :
803 : /**
804 : * @brief The current key the Value is bound to.
805 : *
806 : * @invariant: Is never a null key
807 : */
808 : mutable Key m_key;
809 : };
810 :
811 : template <typename T, typename PolicySetter1, typename PolicySetter2, typename PolicySetter3, typename PolicySetter4,
812 : typename PolicySetter5, typename PolicySetter6>
813 : std::ostream & operator<< (std::ostream & os,
814 : Value<T, PolicySetter1, PolicySetter2, PolicySetter3, PolicySetter4, PolicySetter5, PolicySetter6> const & v)
815 : {
816 0 : os << static_cast<T> (v);
817 : return os;
818 : }
819 : } // namespace kdb
820 :
821 : #endif
|