diff --git a/src/common/advice/index.hpp b/src/common/advice/index.hpp index 19491da32..10f4323c6 100644 --- a/src/common/advice/index.hpp +++ b/src/common/advice/index.hpp @@ -21,7 +21,7 @@ ** ** This header is intended to be incorporated as part of the advice system implementation (advice.cpp). ** It is \em not usable as an external interface. But it is written in a rather self-contained manner, - ** in order to be testable in isolation. To this end, the actual PointOfAdvice entities being organised + ** in order to be testable in isolation. To this end, the actual PointOfAdvice entities \a POA organised ** by this index datastructure remain abstract (defined as template parameter), and are only manipulated ** through the following functions: ** - \c hash_value(POA) @@ -87,7 +87,6 @@ #include "lib/util.hpp" #include "common/advice/binding.hpp" -#include #include #include @@ -122,6 +121,7 @@ namespace advice { * by invoking the \c setSolution() function on the * corresponding PointOfAdvice entity. * + * @tparam POA _point-of-advice_ exposing a matcher and solution * @note element \em identity is defined in terms of pointing * to the same memory location of a POA (point of advice). * Thus e.g. #hasProvision means this index holds an entry @@ -141,12 +141,8 @@ namespace advice { class Index { - struct Entry : pair - , boost::equality_comparable - > { explicit Entry (POA& elm) diff --git a/src/common/query.hpp b/src/common/query.hpp index 5f1b64e0d..1adba1ad9 100644 --- a/src/common/query.hpp +++ b/src/common/query.hpp @@ -76,9 +76,9 @@ #include "lib/util.hpp" #include -#include -#include #include +#include +#include #include #include @@ -136,6 +136,8 @@ namespace lumiera { : kind(k) , type(t) { } + + auto operator<=> (QueryID const&) const =default; }; QueryID const& @@ -185,26 +187,6 @@ namespace lumiera { }; - inline bool - operator< (Goal::QueryID const& id1, Goal::QueryID const& id2) - { - return id1.kind < id2.kind - or(id1.kind == id2.kind and id1.type < id2.type); - } - - inline bool - operator== (Goal::QueryID const& id1, Goal::QueryID const& id2) - { - return id1.kind == id2.kind - and id1.type == id2.type; - } - - inline bool - operator!= (Goal::QueryID const& id1, Goal::QueryID const& id2) - { - return not (id1 == id2); - } - namespace { @@ -386,7 +368,6 @@ namespace lumiera { * Implicitly convertible to and from Query instances. */ class QueryKey - : boost::totally_ordered { Goal::QueryID id_; lib::QueryText def_; @@ -442,21 +423,19 @@ namespace lumiera { } - friend bool - operator< (QueryKey const& q1, QueryKey const& q2) + friend std::strong_ordering + operator<=> (QueryKey const& q1, QueryKey const& q2) { uint d1 = q1.degree(); uint d2 = q2.degree(); - return d1 < d2 - or(d1 == d2 and ( q1.def_ < q2.def_ - or (q1.def_ == q2.def_ and q1.id_ < q2.id_))); - } - - friend bool - operator== (QueryKey const& q1, QueryKey const& q2) - { - return q1.def_ == q2.def_; + if (auto o1 = d1 <=> d2; o1 != 0) + return o1; + if (auto o2 = q1.def_ <=> q2.def_; o2 != 0) + return o2; + else + return q1.id_ <=> q2.id_; } + bool operator== (QueryKey const&) const =default; friend size_t hash_value (QueryKey const& q) diff --git a/src/lib/frameid.hpp b/src/lib/frameid.hpp index 06a001382..fc45cdcbb 100644 --- a/src/lib/frameid.hpp +++ b/src/lib/frameid.hpp @@ -24,6 +24,8 @@ ** @deprecated 10/2024 seems very likely that similar functionality moves down ** into the render-engine implementation and will no longer be considered ** a constituent of the public interface. + ** @todo 6/2025 basically everything here is unused or will likely be done + ** in a different way — expect this and rendergraph.cpp to be obsolete */ @@ -31,7 +33,8 @@ #define LUMIERA_FRAMEID_H -#include +#include "lib/integral.hpp" +#include namespace lumiera { @@ -73,15 +76,14 @@ namespace lumiera { * later on define what is actually needed; this header should then * be replaced by a combined C/C++ header */ - class FrameID : boost::totally_ordered ////////////TODO it seems we don't need total ordering, only comparison. Clarify this! + class FrameID { long dummy; public: FrameID(long dum=0) : dummy(dum) {} operator long () { return dummy; } - bool operator< (const FrameID& other) const { return dummy < other.dummy; } - bool operator== (const FrameID& other) const { return dummy == other.dummy; } + auto operator<=> (FrameID const&) const =default; }; diff --git a/src/lib/idi/entry-id.hpp b/src/lib/idi/entry-id.hpp index 281e92775..4701e9d8f 100644 --- a/src/lib/idi/entry-id.hpp +++ b/src/lib/idi/entry-id.hpp @@ -47,7 +47,7 @@ #include "lib/util.hpp" #include -#include +#include #include @@ -131,7 +131,6 @@ namespace idi { * for building a combined hash and symbolic ID. */ class BareEntryID - : public boost::equality_comparable { string symbol_; @@ -217,7 +216,6 @@ namespace idi { template struct EntryID : BareEntryID - , boost::totally_ordered< EntryID > { /** case-1: auto generated symbolic ID */ @@ -272,15 +270,17 @@ namespace idi { explicit operator string() const; - friend bool operator< (EntryID const& i1, EntryID const& i2) { return i1.getSym() < i2.getSym(); } + friend auto operator<=> (EntryID const& i1, EntryID const& i2) { return i1.getSym() <=> i2.getSym(); } }; - inline bool operator== (BareEntryID const& i1, BareEntryID const& i2) { return i1.getHash() == i2.getHash(); } + // Note: since we allow comparison only between EntryIDs of same type + // and also feed-down the symbol into the hash value, both equality + // and (total) ordering mesh up perfectly. diff --git a/src/lib/query-text.hpp b/src/lib/query-text.hpp index 465410b53..180441c5a 100644 --- a/src/lib/query-text.hpp +++ b/src/lib/query-text.hpp @@ -77,6 +77,7 @@ namespace lib { return definition_; } + auto operator<=> (QueryText const&) const =default; bool empty() const @@ -105,19 +106,6 @@ namespace lib { private: string normalise (string const& rawDefinition); - - - friend bool - operator== (QueryText const& q1, QueryText const& q2) - { - return q1.definition_ == q2.definition_; - } - friend bool - operator< (QueryText const& q1, QueryText const& q2) - { - return q1.definition_ < q2.definition_; - } - friend HashVal hash_value (QueryText const& entry); }; diff --git a/src/lib/time/digxel.hpp b/src/lib/time/digxel.hpp index ddc0cb713..b8c736eb7 100644 --- a/src/lib/time/digxel.hpp +++ b/src/lib/time/digxel.hpp @@ -60,7 +60,6 @@ #include "lib/symbol.hpp" #include "lib/util.hpp" -#include #include #include #include @@ -215,7 +214,6 @@ namespace time { , class FMT = digxel::Formatter > class Digxel - : public boost::totally_ordered> { mutable FMT buffer_; @@ -297,9 +295,9 @@ namespace time { NUM operator++ (int) { NUM p(value_); *this =p+1; return p;} NUM operator-- (int) { NUM p(value_); *this =p-1; return p;} - //---Supporting-totally_ordered--------- - bool operator< (Digxel const& o) const { return value_ < NUM(o); } - bool operator== (Digxel const& o) const { return value_ == NUM(o); } + //---Supporting-total-ordering---------- + auto operator<=>(Digxel const& o) const { return value_ <=> NUM(o); } + bool operator== (Digxel const& o) const { return value_ == NUM(o); } }; diff --git a/src/lib/time/timequant.hpp b/src/lib/time/timequant.hpp index 040cb57b3..1bddcc267 100644 --- a/src/lib/time/timequant.hpp +++ b/src/lib/time/timequant.hpp @@ -62,7 +62,6 @@ #include "lib/time/timecode.hpp" #include "lib/symbol.hpp" -//#include #include diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index d0880005b..fbb85baa3 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -99,6 +99,7 @@ #include #include +#include #include #include @@ -139,8 +140,6 @@ namespace time { * @see TimeVar when full arithmetics are required */ class TimeValue - : boost::totally_ordered> { protected: /** the raw (internal) time value @@ -196,12 +195,12 @@ namespace time { /** @return is in-domain, not a boundary value */ bool isRegular() const; - // Supporting totally_ordered - friend bool operator< (TimeValue const& t1, TimeValue const& t2) { return t1.t_ < t2.t_; } - friend bool operator< (TimeValue const& t1, raw_time_64 t2) { return t1.t_ < t2 ; } - friend bool operator> (TimeValue const& t1, raw_time_64 t2) { return t1.t_ > t2 ; } - friend bool operator== (TimeValue const& t1, TimeValue const& t2) { return t1.t_ == t2.t_; } - friend bool operator== (TimeValue const& t1, raw_time_64 t2) { return t1.t_ == t2 ; } + // Supporting strong total ordering + std::strong_ordering operator<=> (TimeValue const&) const =default; + bool operator== (TimeValue const&) const =default; + + std::strong_ordering operator<=> (raw_time_64 tt) const { return t_ <=> tt; } + bool operator== (raw_time_64 tt) const { return t_ == tt; } }; @@ -582,7 +581,6 @@ namespace time { */ class TimeSpan : public Time - , boost::totally_ordered { Duration dur_; @@ -651,10 +649,15 @@ namespace time { /** @internal diagnostics */ explicit operator std::string() const; - /// Supporting extended total order, based on start and interval length - friend bool operator== (TimeSpan const& t1, TimeSpan const& t2) { return t1.t_==t2.t_ && t1.dur_==t2.dur_; } - friend bool operator< (TimeSpan const& t1, TimeSpan const& t2) { return t1.t_< t2.t_ || - (t1.t_==t2.t_ && t1.dur_< t2.dur_);} + /// Supporting extended strong total ordering, based on start and interval length + std::strong_ordering + operator<=> (TimeSpan const& ts) const + { + auto ord{ t_ <=> ts.t_ }; + return ord != 0? ord + : dur_ <=> ts.dur_; + } + bool operator== (TimeSpan const& ts) const =default; }; diff --git a/src/steam/asset.hpp b/src/steam/asset.hpp index 494f893ce..9bd9c6d2e 100644 --- a/src/steam/asset.hpp +++ b/src/steam/asset.hpp @@ -54,10 +54,8 @@ #include "lib/hash-value.h" #include "lib/p.hpp" -#include -#include - -#include +#include +#include #include #include #include @@ -137,8 +135,7 @@ namespace asset { * @author Ichthyo */ class Asset - : public boost::totally_ordered1< Asset - , util::NonCopyable > + : util::NonCopyable { public: @@ -147,7 +144,6 @@ namespace asset { * sufficiently identifying any given Asset. */ struct Ident - : boost::totally_ordered { /** element ID, comprehensible but sanitised. * The tuple (category, name, org) is unique. @@ -181,11 +177,13 @@ namespace asset { ,const uint ver=1); - int compare (Ident const& other) const; - - /** @note equality ignores version differences */ - bool operator== (Ident const& oi) const { return compare (oi) ==0; } - bool operator< (Ident const& oi) const { return compare (oi) < 0; } + /** ordering of Assets is based on the ordering of Ident tuples, + * descending from Category to origin and finally to the asset element ID. + * @note version info is ignored for this comparison */ + auto operator<=>(Ident const& oi) const { return std::tie ( category, org, name) + <=> std::tie (oi.category,oi.org,oi.name); } + bool operator== (Ident const& oi) const { return 0 == *this <=> oi; } + operator string () const; @@ -201,8 +199,8 @@ namespace asset { virtual const ID& getID() const { return id; } - bool operator== (Asset const& oa) const { return ident == oa.ident; } - bool operator< (Asset const& oa) const { return ident < oa.ident; } + bool operator== (Asset const& oa) const { return ident == oa.ident; } + auto operator<=>(Asset const& oa) const { return ident <=> oa.ident; } virtual operator string () const; @@ -300,24 +298,6 @@ namespace asset { - /* ====== ordering of Assets and Asset-Pointers ====== */ - - /** ordering of Assets is based on the ordering - * of Ident tuples, which are supposed to be unique. - * By using our customised lumiera::P as smart ptr, - * comparison on P ptrs will be automatically - * forwarded to the Asset comparison operators. - * @note version info is irrelevant */ - inline int - Asset::Ident::compare (Asset::Ident const& oi) const - { - int res; - if (0 != (res=category.compare (oi.category))) return res; - if (0 != (res=org.compare (oi.org))) return res; - return name.compare (oi.name); - } - - /** promote subtype-ptr to PAsset, e.g. for comparing */ template inline const PcAsset @@ -329,11 +309,11 @@ namespace asset { /** type trait for detecting a shared-ptr-to-asset */ template - struct is_pAsset : boost::false_type {}; + struct is_pAsset : std::false_type { }; template struct is_pAsset> - : boost::is_base_of {}; + : std::is_base_of { }; /** marker constant denoting a NIL asset */ diff --git a/src/steam/asset/category.hpp b/src/steam/asset/category.hpp index c97abd979..431ba7c72 100644 --- a/src/steam/asset/category.hpp +++ b/src/steam/asset/category.hpp @@ -24,6 +24,7 @@ #include "lib/hash-standard.hpp" #include +#include #include @@ -73,29 +74,15 @@ namespace asset { Category (const Kind root, Literal subfolder ="") : kind_(root), path_(subfolder) {}; - bool operator== (Category const& other) const { return kind_== other.kind_ and path_== other.path_; } - bool operator!= (Category const& other) const { return kind_!= other.kind_ or path_!= other.path_; } + auto operator<=> (Category const&) const =default; bool hasKind (Kind refKind) const { return kind_ == refKind; } bool isWithin (Category const&) const; void setPath (string const& newpath) { this->path_ = newpath; } - operator string () const; friend size_t hash_value (Category const&); - - - int - compare (Category const& co) const - { - int res = int(kind_) - int(co.kind_); - if (0 != res) - return res; - else - return path_.compare (co.path_); - } - }; diff --git a/src/steam/asset/typed-id.hpp b/src/steam/asset/typed-id.hpp index 582ea8b56..4b6a2bcb5 100644 --- a/src/steam/asset/typed-id.hpp +++ b/src/steam/asset/typed-id.hpp @@ -45,7 +45,6 @@ //#include "lib/util.hpp" #include "lib/symbol.hpp" -//#include #include namespace lumiera{ ///////TODO: shouldn't that be namespace lib? or steam? diff --git a/src/steam/control/memento-tie.hpp b/src/steam/control/memento-tie.hpp index 4b821ed46..24e74e8cb 100644 --- a/src/steam/control/memento-tie.hpp +++ b/src/steam/control/memento-tie.hpp @@ -38,7 +38,6 @@ #include "lib/format-obj.hpp" #include "lib/util.hpp" -#include #include #include @@ -47,7 +46,6 @@ namespace steam { namespace control { namespace err = lumiera::error; - using boost::equality_comparable; using lib::meta::func::bindLast; using lib::meta::func::chained; using lib::meta::equals_safeInvoke; @@ -77,7 +75,6 @@ namespace control { */ template class MementoTie - : public equality_comparable> { typedef typename CommandSignature::CaptureSig SIG_cap; typedef typename CommandSignature::UndoOp_Sig SIG_undo; @@ -195,7 +192,7 @@ namespace control { template MementoTie::operator std::string() const { - if (!undo_ or !capture_) + if (not undo_ or not capture_) return "·noUNDO·"; if (not isCaptured_) diff --git a/src/steam/mobject/output-mapping.hpp b/src/steam/mobject/output-mapping.hpp index bccea178b..cc82aec47 100644 --- a/src/steam/mobject/output-mapping.hpp +++ b/src/steam/mobject/output-mapping.hpp @@ -49,7 +49,7 @@ #include "steam/asset/pipe.hpp" #include "common/query.hpp" -#include +#include #include @@ -121,7 +121,7 @@ namespace mobject { class OutputMapping : public DEF { - typedef _def Setup; + using Setup = _def; using PId = asset::ID; using PPipe = asset::PPipe; @@ -148,10 +148,11 @@ namespace mobject { * of the specific resolution functor, embedded in the definition context `DEF`, * which was given when instantiating the OutputMapping template. * @note depends on the template parameter of the enclosing OutputMapping type! + * @remarks + * - final mapping result can be compared to Target + * - Resolvers (mapping values) can be compared based on the Pipe-hash */ class Resolver - : public boost::equality_comparable> // mapping values can be compared. { OutputMapping& thisMapping_; HashVal& pID_; diff --git a/tests/core/steam/asset/asset-category-test.cpp b/tests/core/steam/asset/asset-category-test.cpp index 3dd0eb17f..96a9d2447 100644 --- a/tests/core/steam/asset/asset-category-test.cpp +++ b/tests/core/steam/asset/asset-category-test.cpp @@ -100,25 +100,25 @@ namespace test { Category c5 (STRUCT); Category c6 (META); - CHECK (0 > c1.compare(c2)); - CHECK (0 > c2.compare(c3)); - CHECK (0 > c3.compare(c4)); - CHECK (0 > c4.compare(c5)); - CHECK (0 > c5.compare(c6)); + CHECK (0 > c1 <=> c2 ); + CHECK (0 > c2 <=> c3 ); + CHECK (0 > c3 <=> c4 ); + CHECK (0 > c4 <=> c5 ); + CHECK (0 > c5 <=> c6 ); - CHECK (0 ==c1.compare(c1)); - CHECK (0 > c1.compare(c6)); + CHECK (0 ==c1 <=> c1 ); + CHECK (0 > c1 <=> c6 ); Category c21 (VIDEO,"bin1"); Category c22 (VIDEO,"bin2"); Category c23 (VIDEO,"bin2/sub"); - CHECK (0 > c1.compare(c21)); - CHECK (0 > c2.compare(c21)); - CHECK (0 < c22.compare(c21)); - CHECK (0 < c23.compare(c22)); - CHECK (0 < c23.compare(c21)); - CHECK ( 0==c22.compare(c22)); + CHECK (0 > c1 <=> c21 ); + CHECK (0 > c2 <=> c21 ); + CHECK (0 < c22 <=> c21 ); + CHECK (0 < c23 <=> c22 ); + CHECK (0 < c23 <=> c21 ); + CHECK (0 ==c22 <=> c22 ); CHECK ( c2 == c2 ); diff --git a/tests/core/steam/asset/ordering-of-assets-test.cpp b/tests/core/steam/asset/ordering-of-assets-test.cpp index da96a3676..e0b7f1b2b 100644 --- a/tests/core/steam/asset/ordering-of-assets-test.cpp +++ b/tests/core/steam/asset/ordering-of-assets-test.cpp @@ -84,17 +84,17 @@ namespace test { CHECK (key4 != key5); CHECK (key1 != key5); - CHECK ( 0 > key2.compare(key3)); - CHECK ( 0 < key3.compare(key2)); + CHECK ( 0 > key2 <=> key3 ); + CHECK ( 0 < key3 <=> key2 ); - CHECK ( 0 > key3.compare(key4)); - CHECK ( 0 > key4.compare(key5)); - CHECK ( 0 > key1.compare(key5)); - CHECK ( 0 > key2.compare(key5)); - CHECK ( 0 > key3.compare(key5)); - CHECK ( 0 > key1.compare(key3)); - CHECK ( 0 > key1.compare(key4)); - CHECK ( 0 > key2.compare(key4)); + CHECK ( 0 > key3 <=> key4 ); + CHECK ( 0 > key4 <=> key5 ); + CHECK ( 0 > key1 <=> key5 ); + CHECK ( 0 > key2 <=> key5 ); + CHECK ( 0 > key3 <=> key5 ); + CHECK ( 0 > key1 <=> key3 ); + CHECK ( 0 > key1 <=> key4 ); + CHECK ( 0 > key2 <=> key4 ); // ordering of Asset smart ptrs diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 51a16858d..f6dcfde22 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -157128,7 +157128,80 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + + +

+ strong_ordering::greater +

+

+ strong_ordering::equal +

+

+ strong_ordering::less +

+ +
+ + + + + + + + +

+ weak_ordering::greater +

+

+ weak_ordering::equivalent +

+

+ weak_ordering::less +

+ +
+ + + + + + + + + + +

+ partial_ordering::greater +

+

+ partial_ordering::equivalent +

+

+ partial_ordering::less +

+

+ partial_ordering::unordered +

+

+ +

+ +
+ + + + +
+ + @@ -157148,7 +157221,12 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + @@ -167031,9 +167109,7 @@ Since then others have made contributions, see the log for the history. - - - +

aber Literal kann komplett constexpr sein @@ -167047,10 +167123,343 @@ Since then others have made contributions, see the log for the history. - - + + + + +

+ immerhin: konnte alle komplexen Vergleichsoperatoren viel einfacher per Spaceship darstellen... +

+

+ Trotzdem verwenden wir weiterhin boost::operators (und das ist nicht wirklich problematisch, da header-only) +

+ +
+ + + + + + + + + + + + + + + + + +

+ Template-Parameter POA +

+ +
+ +
+
+ + + + + + + +

+ denn was bedeutet es schon, wenn eine Query »kleiner« ist als eine andere — und dann im Bezug dazu, wenn zwei Queries »gleich« sind? +

+ +
+
+ + + + +

+ und zwar weil letztlich das Prädikaten-Symbol und dann noch zusätzlich die Typ-ID die Entscheidung fällt +

+ + +
+
+ + + + + + +

+ d.h. die drei explizit definieren Operatoren können weg, und dafür wird ein defaulted Spaceship eingeführt +

+ +
+
+ + + + + + + +

+ bisher wurde die Kategorie für die Gleichheit ignoriert, d.h. es wurde nur die Prädikation verglichen; das ist inkonsistent mit der Ordnung, welche diesbezüglich noch differenziert (gleiche Prädikation bei verschiedener Kategorie ist nicht äquivalent) +

+ +
+ +
+ + + +
+
+
+ + + + + + + + + + + + + +

+ ...weil wir stets die 2-Argument-Konstruktoren von BareEntryID aufrufen, und getTypeHash<TY>() dabei als Seed injizieren +

+ +
+
+
+ + + + + + + + + + + + +
+
+

+ /** +

+

+ * Identification tuple for addressing frames unambiguously. +

+

+ * +

+

+ * @todo currently (7/08) this is a dummy implementation to find out +

+

+ * what interface the Steam layer needs. Probably the vault layer will +

+

+ * later on define what is actually needed; this header should then +

+

+ * be replaced by a combined C/C++ header +

+

+ */ +

+

+ class FrameID : boost::totally_ordered<FrameID>  ////////////TODO it seems we don't need total ordering, only comparison. Clarify this! +

+

+ { +

+

+ long dummy; +

+

+ public: +

+

+ FrameID(long dum=0)  : dummy(dum) {} +

+

+ operator long () { return  dummy; } +

+

+ +

+

+ bool operator< (const  FrameID& other) const  { return dummy <  other.dummy; } +

+

+ bool operator== (const  FrameID& other) const  { return dummy ==  other.dummy; } +

+

+ }; +

+

+ +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +

+ STOP: wir verwenden auch boost::additive und boost::multipliable +

+ +
+ + +
+ + + + + + + + + + + + + + +
+ + + + + + +

+ verwendet boost::unit_steppable<SmpteTC> +

+ +
+ +
+ + + +
+ + + + + + + + + + + + +
    +
  • + was ist ein »äquivalentes« Memento? +
  • +
  • + wozu würde man das brauchen? +
  • +
+

+ normalerweise interessiert nur der Zustand, ob überhaupt ein Memento gespeichert wurde +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +

+ ...und er verhält sich wie typischweise in den funktionalen Sprachen; daher ist der Spaceship-Operator ein drop-in replacement, da dessen Ergebnis ja auch mit dem Literal 0 verglichen werden kann +

+ +
+
+ + + + + +
+
+ + + + + + + + +

+ additive und multipliable +

+

+ Und diese sind mit die massivsten Definition in boost::operators — also sicherlich nichts, was man von Hand nachimplementieren möchte +

+ +
+ +
+ + + + + + +