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

Reply via email to