This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch cpp-20 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit 9020f0eed6730acff7cd38b4411c4a5f73e22540 Author: Alan M. Carroll <[email protected]> AuthorDate: Wed Apr 12 20:56:42 2023 -0500 Checkpoint C++20 --- code/include/swoc/Lexicon.h | 184 +++++++++++++++++++++++++++---------------- code/include/swoc/MemArena.h | 14 ++-- code/include/swoc/MemSpan.h | 6 +- code/include/swoc/TextView.h | 4 +- code/src/MemArena.cc | 10 ++- doc/code/Lexicon.en.rst | 51 +++++++++++- example/ex_netdb.cc | 6 +- unit_tests/ex_UnitParser.cc | 2 +- unit_tests/ex_bw_format.cc | 6 +- unit_tests/test_Lexicon.cc | 74 +++++++++++++---- 10 files changed, 251 insertions(+), 106 deletions(-) diff --git a/code/include/swoc/Lexicon.h b/code/include/swoc/Lexicon.h index a15e885..8333b42 100644 --- a/code/include/swoc/Lexicon.h +++ b/code/include/swoc/Lexicon.h @@ -33,10 +33,19 @@ namespace detail { */ template <typename... Args> std::string -what(std::string_view const &fmt, Args &&... args) { +what(std::string_view const &fmt, Args &&...args) { std::string zret; return swoc::bwprint_v(zret, fmt, std::forward_as_tuple(args...)); } + +// Exported because inner classes in template classes cannot be used in partial specialization +// which is required for tuple support. This should be remove next time there is a API changing +// release because tuple access is being deprecated. +template < typename E > struct lexicon_pair_type { + E _value; ///< + TextView _name; +}; + } // namespace detail /// Policy template use to specify the hash function for the integral type of @c Lexicon. @@ -79,8 +88,9 @@ protected: public: /// An association of an enumeration value and a name. /// @ note Used for initializer lists that have just a primary value. - using Pair = std::tuple<E, std::string_view>; + using Pair = detail::lexicon_pair_type<E>; + // Deprecated - use the member names now. /// Index in @c Pair for the enumeration value. static constexpr auto VALUE_IDX = 0; /// Index in @c Pair for name. @@ -95,7 +105,7 @@ public: * without a destructor being called. Unfortunately this can't be done any better without * imposing memory management costs on normal use. */ - using UnknownValueHandler = std::function<std::string_view(E)>; + using UnknownValueHandler = std::function<TextView(E)>; /** A function to be called if a name is not found, to provide a default value. * @param name The name @@ -103,23 +113,27 @@ public: * * The @a name is provided and a value in the enumeration type is expected. */ - using UnknownNameHandler = std::function<E(std::string_view)>; + using UnknownNameHandler = std::function<E(TextView)>; /** A default handler. * * This handles providing a default value or name for a missing name or value. */ - using DefaultHandler = std::variant<std::monostate, E, std::string_view, UnknownNameHandler, UnknownValueHandler>; + using Default = std::variant<std::monostate, E, TextView, UnknownNameHandler, UnknownValueHandler>; /// Element of an initializer list that contains secondary names. + /// @note This is used only in the constructor and contains transient data. struct Definition { - const E &value; ///< Value for definition. - const std::initializer_list<std::string_view> &names; ///< Primary then secondary names. + const E &value; ///< Value for definition. + std::initializer_list<TextView> const &names; ///< Primary then secondary names. }; /// Construct empty instance. Lexicon(); + using with = std::initializer_list<Pair> const &; + using with_multi = std::initializer_list<Definition> const &; + /** Construct with names, possible secondary values, and optional default handlers. * * @param items A list of initializers, each of which is a name and a list of values. @@ -135,8 +149,7 @@ public: * * @see set_default. */ - explicit Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1 = DefaultHandler{}, - DefaultHandler handler_2 = DefaultHandler{}); + explicit Lexicon(with_multi items, Default handler_1 = Default{}, Default handler_2 = Default{}); /** Construct with names / value pairs, and optional default handlers. * @@ -151,8 +164,7 @@ public: * * @see set_default. */ - explicit Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1 = DefaultHandler{}, - DefaultHandler handler_2 = DefaultHandler{}); + explicit Lexicon(with items, Default handler_1 = Default{}, Default handler_2 = Default{}); /** Construct with only default values / handlers. * @@ -164,7 +176,7 @@ public: * * @see set_default. */ - explicit Lexicon(DefaultHandler handler_1, DefaultHandler handler_2 = DefaultHandler{}); + explicit Lexicon(Default handler_1, Default handler_2 = Default{}); Lexicon(self_type &&that) = default; @@ -173,24 +185,24 @@ public: * @param value Value to look up. * @return The name for @a value. */ - std::string_view operator[](E const& value) const; + TextView operator[](E const &value) const; /** Get the value for a @a name. * * @param name Name to look up. * @return The value for the @a name. */ - E operator[](std::string_view const &name) const; + E operator[](TextView const &name) const; /// Define the @a names for a @a value. /// The first name is the primary name. All @a names must be convertible to @c std::string_view. /// <tt>lexicon.define(Value, primary, [secondary, ... ]);</tt> - template <typename... Args> self_type &define(E value, Args &&... names); + template <typename... Args> self_type &define(E value, Args &&...names); // These are really for consistency with constructors, they're not expected to be commonly used. /// Define a value and names. /// <tt>lexicon.define(Value, { primary, [secondary, ...] });</tt> - self_type &define(E value, const std::initializer_list<std::string_view> &names); + self_type &define(E value, const std::initializer_list<TextView> &names); /** Define a name, value pair. * @@ -230,7 +242,7 @@ public: * - A @c DefaultValueHandler. This is a functor that takes a name as a @c string_view and returns * an enumeration value as the value for any name that is not found. */ - self_type &set_default(DefaultHandler const &handler); + self_type &set_default(Default const &handler); /// Get the number of values with definitions. size_t count() const; @@ -239,11 +251,12 @@ protected: /// Common features of container iterators. class base_iterator { using self_type = base_iterator; + public: - using value_type = const Pair; ///< Iteration value. - using pointer = value_type *; ///< Pointer to iteration value. - using reference = value_type &; ///< Reference to iteration value. - using difference_type = ptrdiff_t; ///< Type of difference between iterators. + using value_type = const Pair; ///< Iteration value. + using pointer = value_type *; ///< Pointer to iteration value. + using reference = value_type &; ///< Reference to iteration value. + using difference_type = ptrdiff_t; ///< Type of difference between iterators. using iterator_category = std::bidirectional_iterator_tag; ///< Concepts for iterator. /// Default constructor (invalid iterator) base_iterator() = default; @@ -257,24 +270,23 @@ protected: bool operator!=(self_type const &that) const; protected: - explicit base_iterator(Item const * item) : _item(item) {} + explicit base_iterator(Item const *item) : _item(item) {} - const Item *_item{nullptr}; ///< Current location in the container. + const Item *_item{nullptr}; ///< Current location in the container. }; public: - /** Iterator over pairs of values and primary name pairs. * The value type is a @c Pair with the value and name. */ class value_iterator : public base_iterator { using super_type = base_iterator; - using self_type = value_iterator; + using self_type = value_iterator; public: - using value_type = typename super_type::value_type; - using pointer = typename super_type::pointer; - using reference = typename super_type::reference; + using value_type = typename super_type::value_type; + using pointer = typename super_type::pointer; + using reference = typename super_type::reference; /// Default constructor. value_iterator() = default; @@ -301,15 +313,16 @@ public: self_type operator--(int); protected: - value_iterator(const Item *item) : super_type(item) {}; ///< Internal constructor. + value_iterator(const Item *item) : super_type(item){}; ///< Internal constructor. friend Lexicon; }; class name_iterator : public base_iterator { private: - using self_type = name_iterator; + using self_type = name_iterator; using super_type = base_iterator; + public: /// Default constructor. name_iterator() = default; @@ -336,7 +349,7 @@ public: self_type operator--(int); protected: - name_iterator(const Item *item) : super_type(item) {}; ///< Internal constructor. + name_iterator(const Item *item) : super_type(item){}; ///< Internal constructor. friend Lexicon; }; @@ -354,17 +367,29 @@ public: const_iterator end() const; /// Iteration over names - every value/name pair. - name_iterator begin_names() const { return { _by_name.begin() }; } + name_iterator + begin_names() const { + return {_by_name.begin()}; + } /// Iteration over names - every value/name pair. - name_iterator end_names() const { return { _by_name.end() }; } + name_iterator + end_names() const { + return {_by_name.end()}; + } /// @cond INTERNAL // Helper struct to return to enable container iteration for names. struct ByNameHelper { - self_type const & _lexicon; - ByNameHelper(self_type const & self) : _lexicon(self) {} - name_iterator begin() const { return _lexicon.begin_names(); } - name_iterator end() const { return _lexicon.end_names(); } + self_type const &_lexicon; + ByNameHelper(self_type const &self) : _lexicon(self) {} + name_iterator + begin() const { + return _lexicon.begin_names(); + } + name_iterator + end() const { + return _lexicon.end_names(); + } }; /// @endcond @@ -379,7 +404,10 @@ public: * @endcode * @return Temporary. */ - ByNameHelper by_names() const { return { *this }; } + ByNameHelper + by_names() const { + return {*this}; + } protected: /// Handle providing a default name. @@ -399,7 +427,7 @@ protected: /// Visitor - literal string. std::string_view - operator()(std::string_view const &name) const { + operator()(TextView const &name) const { return name; } @@ -442,7 +470,7 @@ protected: * @param name The name. * */ - Item(E value, std::string_view name); + Item(E value, TextView name); Pair _payload; ///< Enumeration and name. @@ -474,7 +502,7 @@ protected: }; /// Copy @a name in to local storage. - std::string_view localize(std::string_view const &name); + TextView localize(TextView const &name); /// Storage for names. MemArena _arena{1024}; @@ -492,7 +520,7 @@ protected: // ---- // Item -template <typename E> Lexicon<E>::Item::Item(E value, std::string_view name) : _payload(value, name) {} +template <typename E> Lexicon<E>::Item::Item(E value, TextView name) : _payload{value, name} {} /// @cond INTERNAL_DETAIL template <typename E> @@ -522,13 +550,13 @@ Lexicon<E>::Item::ValueLinkage::prev_ptr(Item *item) -> Item *& { template <typename E> std::string_view Lexicon<E>::Item::NameLinkage::key_of(Item *item) { - return std::get<NAME_IDX>(item->_payload); + return item->_payload._name; } template <typename E> E Lexicon<E>::Item::ValueLinkage::key_of(Item *item) { - return std::get<VALUE_IDX>(item->_payload); + return item->_payload._value; } template <typename E> @@ -561,8 +589,7 @@ Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs) { template <typename E> Lexicon<E>::Lexicon() {} -template <typename E> -Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, DefaultHandler handler_1, DefaultHandler handler_2) { +template <typename E> Lexicon<E>::Lexicon(with_multi items, Default handler_1, Default handler_2) { for (auto const &item : items) { this->define(item.value, item.names); } @@ -572,8 +599,7 @@ Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, DefaultHandl } } -template <typename E> -Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler handler_1, DefaultHandler handler_2) { +template <typename E> Lexicon<E>::Lexicon(with items, Default handler_1, Default handler_2) { for (auto const &item : items) { this->define(item); } @@ -583,49 +609,49 @@ Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler han } } -template <typename E> Lexicon<E>::Lexicon(DefaultHandler handler_1, DefaultHandler handler_2) { +template <typename E> Lexicon<E>::Lexicon(Default handler_1, Default handler_2) { for (auto &&h : {handler_1, handler_2}) { this->set_default(h); } } template <typename E> -std::string_view -Lexicon<E>::localize(std::string_view const &name) { +TextView +Lexicon<E>::localize(TextView const &name) { auto span = _arena.alloc_span<char>(name.size()); memcpy(span, name); - return { span.data(), span.size() }; + return {span.data(), span.size()}; } template <typename E> -std::string_view -Lexicon<E>::operator[](E const& value) const { - if ( auto spot = _by_value.find(value) ; spot != _by_value.end()) { - return std::get<NAME_IDX>(spot->_payload); +TextView +Lexicon<E>::operator[](E const &value) const { + if (auto spot = _by_value.find(value); spot != _by_value.end()) { + return spot->_payload._name; } return std::visit(NameDefaultVisitor{value}, _name_default); } template <typename E> E -Lexicon<E>::operator[](std::string_view const &name) const { - if ( auto spot = _by_name.find(name) ; spot != _by_name.end()) { - return std::get<VALUE_IDX>(spot->_payload); +Lexicon<E>::operator[](TextView const &name) const { + if (auto spot = _by_name.find(name); spot != _by_name.end()) { + return spot->_payload._value; } return std::visit(ValueDefaultVisitor{name}, _value_default); } template <typename E> auto -Lexicon<E>::define(E value, const std::initializer_list<std::string_view> &names) -> self_type & { +Lexicon<E>::define(E value, const std::initializer_list<TextView> &names) -> self_type & { if (names.size() < 1) { throw std::invalid_argument("A defined value must have at least a primary name"); } - for (auto const& name : names) { + for (auto const &name : names) { if (_by_name.find(name) != _by_name.end()) { throw std::invalid_argument(detail::what("Duplicate name '{}' in Lexicon", name)); } - auto i = new Item(value, this->localize(name)); + auto i = _arena.make<Item>(value, this->localize(name)); _by_name.insert(i); // Only put primary names in the value table. if (_by_value.find(value) == _by_value.end()) { @@ -638,7 +664,7 @@ Lexicon<E>::define(E value, const std::initializer_list<std::string_view> &names template <typename E> template <typename... Args> auto -Lexicon<E>::define(E value, Args &&... names) -> self_type & { +Lexicon<E>::define(E value, Args &&...names) -> self_type & { static_assert(sizeof...(Args) > 0, "A defined value must have at least a primary name"); return this->define(value, {std::forward<Args>(names)...}); } @@ -646,7 +672,7 @@ Lexicon<E>::define(E value, Args &&... names) -> self_type & { template <typename E> auto Lexicon<E>::define(const Pair &pair) -> self_type & { - return this->define(std::get<VALUE_IDX>(pair), {std::get<NAME_IDX>(pair)}); + return this->define(pair._value, pair._name); } template <typename E> @@ -657,7 +683,7 @@ Lexicon<E>::define(const Definition &init) -> self_type & { template <typename E> auto -Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type & { +Lexicon<E>::set_default(Default const &handler) -> self_type & { switch (handler.index()) { case 0: break; @@ -813,4 +839,30 @@ bwformat(BufferWriter &w, bwf::Spec const &spec, Lexicon<E> const &lex) { return w; } -}} // namespace swoc::SWOC_VERSION_NS +}} // namespace swoc + +namespace std { + +template <size_t IDX, typename E> class tuple_element { static_assert("swoc::Lexicon::Pair tuple index out of range"); }; + +template <typename E> class tuple_element<0, swoc::detail::lexicon_pair_type<E>> { +public: + using type = E; +}; + +template <typename E> class tuple_element<1, swoc::detail::lexicon_pair_type<E>> { +public: + using type = swoc::TextView; +}; + +template <size_t IDX, typename E> +auto +get(swoc::detail::lexicon_pair_type<E> const &p) -> typename std::tuple_element<IDX, swoc::detail::lexicon_pair_type<E>>::type { + if constexpr (IDX == 0) { + return p._value; + } else if constexpr (IDX == 1) { + return p._name; + } +} + +} diff --git a/code/include/swoc/MemArena.h b/code/include/swoc/MemArena.h index 1342393..eabd0f1 100644 --- a/code/include/swoc/MemArena.h +++ b/code/include/swoc/MemArena.h @@ -7,6 +7,10 @@ #pragma once +#include "swoc/MemSpan.h" +#include "swoc/Scalar.h" +#include "swoc/IntrusiveDList.h" + #include <mutex> #include <memory> #include <utility> @@ -15,10 +19,6 @@ #include <memory_resource> #endif -#include "swoc/MemSpan.h" -#include "swoc/Scalar.h" -#include "swoc/IntrusiveDList.h" - namespace swoc { inline namespace SWOC_VERSION_NS { /** A memory arena. @@ -38,9 +38,9 @@ class MemArena public: static constexpr size_t DEFAULT_ALIGNMENT{1}; ///< Default memory alignment. - /// Functor for destructing a self contained arena. - /// @see MemArena::unique_ptr - static inline auto destroyer = std::destroy_at<MemArena>; + /// Convenient alias for use with @c unique_ptr. + /// @internal Can't be @c inline because the instantiation requires a complete type. + static void (*destroyer)(self_type *); /// Correct type for a unique pointer to an instance. /// Initialization is diff --git a/code/include/swoc/MemSpan.h b/code/include/swoc/MemSpan.h index 6c5711f..8b71e0c 100644 --- a/code/include/swoc/MemSpan.h +++ b/code/include/swoc/MemSpan.h @@ -9,6 +9,9 @@ #pragma once +#include "swoc/swoc_version.h" +#include "swoc/Scalar.h" + #include <cstring> #include <memory> #include <type_traits> @@ -21,9 +24,6 @@ #include <vector> #include <string_view> -#include "swoc/swoc_version.h" -#include "swoc/Scalar.h" - namespace swoc { inline namespace SWOC_VERSION_NS { /** A span of contiguous piece of memory. diff --git a/code/include/swoc/TextView.h b/code/include/swoc/TextView.h index ce82402..4ee55fa 100644 --- a/code/include/swoc/TextView.h +++ b/code/include/swoc/TextView.h @@ -78,8 +78,10 @@ public: * @param last End of half open range. * * The character at @a first will be in the view, but the character at @a last will not. + * + * @note @c explicit to avoid interpreting a string initializer list as a view. */ - constexpr TextView(char const *first, char const *last) noexcept; + explicit constexpr TextView(char const *first, char const *last) noexcept; /** Construct from any character container following STL standards. * diff --git a/code/src/MemArena.cc b/code/src/MemArena.cc index 3bfd636..033f209 100644 --- a/code/src/MemArena.cc +++ b/code/src/MemArena.cc @@ -5,11 +5,13 @@ MemArena memory allocator. Chunks of memory are allocated, frozen into generations and thawed away when unused. */ -#include <algorithm> #include "swoc/MemArena.h" +#include <algorithm> namespace swoc { inline namespace SWOC_VERSION_NS { +void (*MemArena::destroyer)(MemArena*) = std::destroy_at<MemArena>; + inline bool MemArena::Block::satisfies(size_t n, size_t align) const { auto r = this->remaining(); @@ -173,9 +175,10 @@ MemArena::require(size_t n, size_t align) { void MemArena::destroy_active() { + auto sb = _static_block; // C++20 nonsense - capture of @a this is incompatible with C++17. _active .apply([=](Block *b) { - if (b != _static_block) + if (b != sb) delete b; }) .clear(); @@ -183,9 +186,10 @@ MemArena::destroy_active() { void MemArena::destroy_frozen() { + auto sb = _static_block; // C++20 nonsense - capture of @a this is incompatible with C++17. _frozen .apply([=](Block *b) { - if (b != _static_block) + if (b != sb) delete b; }) .clear(); diff --git a/doc/code/Lexicon.en.rst b/doc/code/Lexicon.en.rst index 5ee9794..e8ffd59 100644 --- a/doc/code/Lexicon.en.rst +++ b/doc/code/Lexicon.en.rst @@ -109,10 +109,10 @@ and :code:`end` methods which return name iterators. This makes container iterat Constructing ============ -To make the class more flexible it can be constructed in a variety of ways. For static the entire +To make the class more flexible it can be constructed in a variety of ways. For a static instance the entire class can be initialized in the constructor. For dynamic use any subset can be initialized. In the previous example, the instance was initialized with all of the defined values and a default -for missing names. Because this fully constructs it, it can be marked ``const`` to prevent +for missing names. Because this fully constructs the Lexicon, it can be marked ``const`` to prevent accidental changes. It could also have been constructed with a default name: .. literalinclude:: ../../unit_tests/ex_Lexicon.cc @@ -120,7 +120,7 @@ accidental changes. It could also have been constructed with a default name: :end-before: doc.ctor.1.end Note the default name was put before the default value. Because they are distinct types, the -defaults can be added in either order, but must always follow the field defintions. The defaults can +defaults can be added in either order, but must always follow the field definitions. The defaults can also be omitted entirely, which is common if the Lexicon is used for output and not parsing, where the enumeration is always valid because all enumeration values are in the Lexicon. @@ -128,7 +128,7 @@ the enumeration is always valid because all enumeration values are in the Lexico :start-after: doc.ctor.2.begin :end-before: doc.ctor.2.end -For dynamic use, it is common to have just the defaults, and not any of the fields, although of course +For dynamic use, it is common to have just the defaults in the constructor, and not any of the fields, although of course if some "built in" names and values are needed those can be added as in the previous examples. .. literalinclude:: ../../unit_tests/ex_Lexicon.cc @@ -154,6 +154,49 @@ because only the ``true`` and ``false`` values would be stored, ``INVALID`` indi error. The enumeration values were chosen so casting from ``bool`` to ``BoolTag`` yields the appropriate string. +C++20 Notes +----------- + +Due to changes in the language some initializations that compile in C++17 become ambigous in C++20 +although I think this is due to a compiler bug in g++ (this problem has not occurred in Clang). +To provide a work around, the type definitions :code:`with` and :code:`with_multi` are exported +from `Lexicon` to force the field initialization list to be a specific type, avoiding the +ambiguity. + +.. literalinclude:: ../../unit_tests/test_Lexicon.cc + :start-after: doc.cpp.20.alpha.start + :end-before: doc.cpp.20.alpha.end + +This issue only arises if none of the multiple name lists are longer than two elements. For instance +this example doesn't require :code:`with_multi` because the first list of names has three elements. + +.. literalinclude:: ../../unit_tests/test_Lexicon.cc + :start-after: doc.cpp.20.alpha.start + :end-before: doc.cpp.20.alpha.end + +.. note:: Techno-babble + + The base issue is the code:`std::string_view` constructor, new in C++20, that takes two iterators + and constructs the view in the standard STL half open way. This makes the following ambiguous for + the argument types :code:`std::string_view` and :code:`std::initializer_list<std::string_view>` :: + + { "alpha", "bravo" } + + This can be read as :: + + std::string_view{char const*, char const*) + + which satisfies the two iterator constructor. In C++17 such a list would never satisfy a + :code:`std::string_view` constructor and so was unambiguously a list of names and not a single + name. The internal fix was to use :libswoc:`TextView` which has that constructor and mark that + constructor :code:`explicit`. This doesn't fully work for g++ which still thinks the list is + ambigous even though *explicitly* using the single name structure :code:`Lexicon::Pair` doesn't + compile. That is, it only compiles in the g++ compiler's imagination, not in actual code. + + As for the idea of using variadic templates to pick off the field definitions one by one, that doesn't + work because the compiler needs to decide the type of all the arguments before picking the + constructor, but it can't do that until after it's already picked the variadic constructor. + Examples ======== diff --git a/example/ex_netdb.cc b/example/ex_netdb.cc index 56da561..f0cf654 100644 --- a/example/ex_netdb.cc +++ b/example/ex_netdb.cc @@ -86,19 +86,19 @@ enum class PodType { }; /// Mapping of names and property flags. -swoc::Lexicon<Flag> FlagNames {{ +swoc::Lexicon<Flag> FlagNames {decltype(FlagNames)::with_multi{ {Flag::NONE, {"-", "NONE"}} , {Flag::INTERNAL, { "internal" }} , {Flag::PROD, {"prod"}} , {Flag::DMZ, {"dmz"}} , {Flag::SECURE, {"secure"}} - }}; + }, Flag::INVALID}; /// Mapping of names and pod types. swoc::Lexicon<PodType> PodTypeNames {{ {PodType::YAHOO, "yahoo"} , {PodType::PARTNER, "partner"} - }}; + }, PodType::INVALID}; // Create BW formatters for the types so they can be used for output. namespace swoc { diff --git a/unit_tests/ex_UnitParser.cc b/unit_tests/ex_UnitParser.cc index e85422e..43613db 100644 --- a/unit_tests/ex_UnitParser.cc +++ b/unit_tests/ex_UnitParser.cc @@ -180,7 +180,7 @@ TEST_CASE("UnitParser Time", "[Lexicon][UnitParser]") { } TEST_CASE("UnitParser Eggs", "[Lexicon][UnitParser]") { - const UnitParser eggs{UnitParser::Units{{ {1, { "egg", "eggs"}}, {12, {"dozen"} }, {12 * 12, { "gross" }}}}, UnitParser::UNITS_NOT_REQUIRED}; + const UnitParser eggs{UnitParser::Units{UnitParser::Units::with_multi{ {1, { "egg", "eggs"}}, {12, {"dozen"} }, {12 * 12, { "gross" }}}}, UnitParser::UNITS_NOT_REQUIRED}; REQUIRE(eggs("1") == 1); REQUIRE(eggs("6") == 6); diff --git a/unit_tests/ex_bw_format.cc b/unit_tests/ex_bw_format.cc index 024ef77..df3de82 100644 --- a/unit_tests/ex_bw_format.cc +++ b/unit_tests/ex_bw_format.cc @@ -118,7 +118,11 @@ struct Context { {"Connection", "keep-alive"}, {"Age", "956"}, {"ETag", "1337beef"}}}; - Fields cookie_fields = {{{"A", "alpha"}, {"B", "bravo"}}}; + static inline std::string A{"A"}; + static inline std::string alpha{"alpha"}; + static inline std::string B{"B"}; + static inline std::string bravo{"bravo"}; + Fields cookie_fields = {{{A, alpha}, {B, bravo}}}; }; } // namespace diff --git a/unit_tests/test_Lexicon.cc b/unit_tests/test_Lexicon.cc index c8c14c1..3f7a7af 100644 --- a/unit_tests/test_Lexicon.cc +++ b/unit_tests/test_Lexicon.cc @@ -19,28 +19,64 @@ using ExampleNames = swoc::Lexicon<Example>; namespace { -[[maybe_unused]] ExampleNames Static_Names { - {Example::Value_0, {"zero", "0"}}, {Example::Value_1, {"one", "1"}}, {Example::Value_2, {"two", "2"}}, - {Example::Value_3, {"three", "3"}}, + +// C++20: This compiles because one of the lists has more than 2 elements. +// I think it's a g++ bug - it compiles in clang. The @c TextView constructor being used +// is marked @c explicit and so it should be discarded. If the constructor is used explicitly +// then it doesn't compile as intended. Therefore g++ is accepting a constructor that doesn't work. +// doc.cpp.20.bravo.start +[[maybe_unused]] ExampleNames Static_Names_Basic{ { - Example::INVALID, { "INVALID" } + {Example::Value_0, {"zero", "0", "none"}}, + {Example::Value_1, {"one", "1"}}, + {Example::Value_2, {"two", "2"}}, + {Example::Value_3, {"three", "3"}}, + {Example::INVALID, {"INVALID"}} } }; -} +// doc.cpp.20.bravo.end + +// C++20: g++ - Without the extra name, must use @cwith_multi. +// doc.cpp.20.alpha.start +[[maybe_unused]] ExampleNames Static_Names_Multi{ + ExampleNames::with_multi{ + {Example::Value_0, {"zero", "0"}}, + {Example::Value_1, {"one", "1"}}, + {Example::Value_2, {"two", "2"}}, + {Example::Value_3, {"three", "3"}}, + {Example::INVALID, {"INVALID"}} + } +}; +// doc.cpp.20.alpha.end + +// If the type isn't easily accessible. +[[maybe_unused]] ExampleNames Static_Names_Decl{ + decltype(Static_Names_Decl)::with_multi{ + {Example::Value_0, {"zero", "0"}}, + {Example::Value_1, {"one", "1"}}, + {Example::Value_2, {"two", "2"}}, + {Example::Value_3, {"three", "3"}}, + {Example::INVALID, {"INVALID"}} + } +}; +} // namespace TEST_CASE("Lexicon", "[libts][Lexicon]") { - ExampleNames exnames{{Example::Value_0, {"zero", "0"}}, + ExampleNames exnames{ExampleNames::with_multi{ + {Example::Value_0, {"zero", "0"}}, {Example::Value_1, {"one", "1"}}, {Example::Value_2, {"two", "2"}}, - {Example::Value_3, {"three", "3"}}, - {Example::INVALID, {"INVALID"}}}; + {Example::Value_3, {"three", "3"}}}, + Example::INVALID, "INVALID" + }; - ExampleNames exnames2{{Example::Value_0, "zero"}, - {Example::Value_1, "one"}, - {Example::Value_2, "two"}, - {Example::Value_3, "three"}, - {Example::INVALID, "INVALID"}}; + ExampleNames exnames2{{{Example::Value_0, {"zero", "nil"}}, + {Example::Value_1, {"one", "single", "mono"}}, + {Example::Value_2, {"two", "double"}}, + {Example::Value_3, {"three", "triple", "3-tuple"}}}, + Example::INVALID, "INVALID" + }; // Check constructing with just defaults. ExampleNames def_names_1 { Example::INVALID }; @@ -67,11 +103,13 @@ TEST_CASE("Lexicon", "[libts][Lexicon]") enum class Radio { INVALID, ALPHA, BRAVO, CHARLIE, DELTA }; using Lex = swoc::Lexicon<Radio>; - Lex lex({{Radio::INVALID, {"Invalid"}}, + Lex lex(Lex::with_multi{ + {Radio::INVALID, {"Invalid"}}, {Radio::ALPHA, {"Alpha"}}, {Radio::BRAVO, {"Bravo", "Beta"}}, {Radio::CHARLIE, {"Charlie"}}, - {Radio::DELTA, {"Delta"}}}); + {Radio::DELTA, {"Delta"}} + }); // test structured binding for iteration. for ([[maybe_unused]] auto const &[key, name] : lex) { @@ -90,7 +128,7 @@ using HexLexicon = swoc::Lexicon<Hex>; TEST_CASE("Lexicon Constructor", "[libts][Lexicon]") { // Construct with a secondary name for NoValue - ValueLexicon vl{{NoValue, {"NoValue", "garbage"}}, {LowValue, {"LowValue"}}}; + ValueLexicon vl{ValueLexicon::with_multi{{NoValue, {"NoValue", "garbage"}}, {LowValue, {"LowValue"}}}}; REQUIRE("LowValue" == vl[LowValue]); // Primary name REQUIRE(NoValue == vl["NoValue"]); // Primary name @@ -145,7 +183,7 @@ TEST_CASE("Lexicon Constructor", "[libts][Lexicon]") REQUIRE(bad_value_p == false); ll_1.define({D, "D"}); // Pair style - ll_1.define({F, {"F", "0xf"}}); // Definition style + ll_1.define(LL::Definition{F, {"F", "0xf"}}); // Definition style REQUIRE(ll_1[D] == "D"); REQUIRE(ll_1["0XF"] == F); @@ -178,6 +216,7 @@ TEST_CASE("Lexicon Constructor", "[libts][Lexicon]") }; +#if 0 TEST_CASE("Lexicon Constructor 2", "[libts][Lexicon]") { // Check the various construction cases @@ -200,3 +239,4 @@ TEST_CASE("Lexicon Constructor 2", "[libts][Lexicon]") REQUIRE(v5["q"] == INVALID); REQUIRE(v5[C] == "Invalid"); } +#endif
