This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch dev-1-0-9
in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git

commit 968de363c1697a521772d550f97664690758f264
Author: Alan M. Carroll <[email protected]>
AuthorDate: Fri Oct 18 18:40:14 2019 -0500

    Lexicon: Change default handlers to make them available for constant 
instances.
---
 swoc++/include/swoc/Lexicon.h | 436 +++++++++++++++++-------------------------
 unit_tests/test_Lexicon.cc    |  37 +++-
 2 files changed, 204 insertions(+), 269 deletions(-)

diff --git a/swoc++/include/swoc/Lexicon.h b/swoc++/include/swoc/Lexicon.h
index 96351d0..b66360e 100644
--- a/swoc++/include/swoc/Lexicon.h
+++ b/swoc++/include/swoc/Lexicon.h
@@ -23,6 +23,8 @@
 #include <tuple>
 #include <functional>
 #include <array>
+#include <variant>
+
 #include "swoc/IntrusiveHashMap.h"
 #include "swoc/MemArena.h"
 #include "swoc/bwf_base.h"
@@ -38,6 +40,8 @@ namespace detail
    * @param fmt Format string.
    * @param args Arguments to format string.
    * @return r-value reference to a @c std::string containing the formatted 
string.
+   *
+   * This is used when throwing exceptions.
    */
   template <typename... Args>
   std::string
@@ -62,6 +66,12 @@ namespace detail
     @c TRUE would always be "true", while converting any of "true", "1", 
"yes", or "enable" would
     yield @c TRUE. This is convenient for parsing configurations to be more 
tolerant of input.
 
+    The constructors are a bit baroque, but this is necessary in order to be 
able to declare
+    constant instances of the @c Lexicon. If this isn't necessary, everything 
that can be done via
+    the constructor can be done with other methods. The implementation of the 
constructors consists
+    entirely of calls to @c define and @c set_default, the only difference is 
these methods can
+    be called on a @c const instance from there.
+
     @note All names and value must be unique across the Lexicon. All name 
comparisons are case
     insensitive.
  */
@@ -73,34 +83,76 @@ protected:
   struct Item;
 
 public:
-  /// Used for initializer lists that have just a primary value.
-  using Pair = std::tuple<E, std::string_view>;
-  /// A function to be called if a value is not found.
+  /** A function to be called if a value is not found to provide a default 
name.
+   * @param value The value.
+   * @return A name for the value.
+   *
+   * The name is return by view and therefore managing the lifetime of the 
name is problematic.
+   * Generally it should be process lifetime, unless some other shorter 
lifetime can be managed
+   * 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)>;
-  /// A function to be called if a name is not found.
+
+  /** A function to be called if a name is not found, to provide a default 
value.
+   * @param name The name
+   * @return An enumeration value.
+   *
+   * The @a name is provided and a value in the enumeration type is expected.
+   */
   using UnknownNameHandler = std::function<E(std::string_view)>;
 
+  /** 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>;
+
+  /// Used for initializer lists that have just a primary value.
+  using Pair = std::tuple<E, std::string_view>;
+
   /// Element of an initializer list that contains secondary names.
   struct Definition {
     const E &value;                                       ///< Value for 
definition.
     const std::initializer_list<std::string_view> &names; ///< Primary then 
secondary names.
   };
 
-  /// Template argument carrying struct.
-  /// @note Needed to pass a compile time constant to a constructor as compile 
time constant.
-  template <E e> struct Require {
-  };
-
   /// Construct empty instance.
   Lexicon();
-  /// Construct with secondary names.
-  explicit Lexicon(const std::initializer_list<Definition> &items);
-  /// Construct with primary names only.
-  explicit Lexicon(const std::initializer_list<Pair> &items);
-  /// Construct and verify the number of definitions.
-  template <E e> Lexicon(const Require<e> &, const std::array<Definition, 
static_cast<size_t>(e)> &defines);
-  /// Construct and verify the number of pairs.
-  template <E e> Lexicon(const Require<e> &, const std::array<Pair, 
static_cast<size_t>(e)> &defines);
+
+  /** 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.
+   * @param handler_1 A default handler.
+   * @param handler_2 A default hander.
+   *
+   * Each item in the intializers must be a @c Definition, that is a name and 
a list of values.
+   * The first value is the primary value and is required. Subsequent values 
are optional
+   * and become secondary values.
+   *
+   * The default handlers are optional can be be omitted. If so, exceptions 
are thrown when values
+   * or names not in the @c Lexicon are used. See @c set_default for more 
details.
+   *
+   * @see set_default.
+   */
+  explicit Lexicon(const std::initializer_list<Definition> &items, 
DefaultHandler handler_1 = DefaultHandler{},
+                   DefaultHandler handler_2 = DefaultHandler{});
+
+  /** Construct with names / value pairs, and optional default handlers.
+   *
+   * @param items A list of initializers, each of which is a name and a list 
of values.
+   * @param handler_1 A default handler.
+   * @param handler_2 A default hander.
+   *
+   * Each item in the intializers must be a @c Pair, that is a name and a 
value.
+   *
+   * The default handlers are optional can be be omitted. If so, exceptions 
are thrown when values
+   * or names not in the @c Lexicon are used. See @c set_default for more 
details.
+   *
+   * @see set_default.
+   */
+  explicit Lexicon(const std::initializer_list<Pair> &items, DefaultHandler 
handler_1 = DefaultHandler{},
+                   DefaultHandler handler_2 = DefaultHandler{});
 
   /** Get the name for a @a value.
    *
@@ -124,45 +176,46 @@ public:
   /// 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(const Pair &pair);
-  self_type &define(const Definition &init);
 
-  /** Set a default @a value.
+  /** Define a name, value pair.
    *
-   * @param value Value to return if a name is not found.
-   * @return @c *this
+   * @param pair A @c Pair of the name and value to define.
+   * @return @a this.
    */
-  self_type &set_default(E value);
+  self_type &define(const Pair &pair);
 
-  /** Set a default @a name.
+  /** Define a name with a primary and secondary values.
    *
-   * @param name Name to return if a value is not found.
-   * @return @c *this
+   * @param init The @c Definition with the name and values.
+   * @return @a this.
    *
-   * @note The @a name is copied to local storage.
+   * This defines the name, with the first value in the value list becoming 
the primary value
+   * and subsequent values (if any) being the secondary values. A primary 
value is required but
+   * secondary values are not. This is to make it possible to define all 
values in this style
+   * even if some do not have secondary values.
    */
-  self_type &set_default(std::string_view name);
+  self_type &define(const Definition &init);
 
-  /** Set a default @a handler for names that are not found.
+  /** Set default handler.
    *
-   * @param handler Function to call with a name that was not found.
-   * @return @c this
+   * @param def The handler.
+   * @return @a this.
    *
-   * @a handler is passed the name that was not found as a @c std::string_view 
and must return a
-   * value which is then returned to the caller.
-   */
-  self_type &set_default(const UnknownNameHandler &handler);
-
-  /** Set a default @a handler for values that are not found.
+   * The @a handler can be of various types.
    *
-   * @param handler Function to call with a value that was not found.
-   * @return @c *this
+   * - An enumeration value. This sets the default value handler to return 
that value for any
+   *   name not found.
    *
-   * @a handler is passed the value that was not found and must return a name 
as a @c std::string_view.
-   * Caution must be used because the returned name must not leak and must be 
thread safe. The most
-   * common use would be for logging bad values.
+   * - A @c string_view. The sets the default name handler to return the @c 
string_view as the
+   *   name for any value not found.
+   *
+   * - A @c DefaultNameHandler. This is a functor that takes an enumeration 
value parameter and
+   *   returns a @c string_view as the name for any value that is not found.
+   *
+   * - 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(const UnknownValueHandler &handler);
+  self_type &set_default(DefaultHandler const &handler);
 
   /// Get the number of values with definitions.
   size_t count() const;
@@ -205,89 +258,74 @@ public:
     friend Lexicon;
   };
 
-  // There is no modifying elements in the Lexicon so only constant iteration.
+  // Only constant iteratior allowed, values cannot be modified.
   using iterator = const_iterator;
 
   const_iterator begin() const;
   const_iterator end() const;
 
 protected:
-  // Because std::variant is broken up through clang 6, we have to do 
something uglier.
-  //  using NameDefault  = std::variant<std::monostate, std::string_view, 
UnknownValueHandler>;
-  //  using ValueDefault = std::variant<std::monostate, E, UnknownNameHandler>;
-
-  /// Type marker for internal variant.
-  enum class Content {
-    NIL,    ///< Nothing, not set.
-    SCALAR, ///< A specific value/name.
-    HANDLER ///< A function
-  };
-
-  /// Default (no value) struct for variant initialization.
-  struct NilValue {
-  };
-
-  /// Handler for values that are not in the Lexicon.
-  struct NameDefault {
-    using self_type = NameDefault; ///< Self reference type.
-
-    NameDefault() = default; ///< Default constructor.
-    ~NameDefault();          ///< Destructor.
-
-    /** Set the handler to return a fixed value.
-     *
-     * @param name Name to return.
-     * @return @a this
-     */
-    self_type &operator=(std::string_view name);
-
-    /** Set the handler to call a function to compute the default name.
-     *
-     * @param handler Handler called to compute the name.
-     * @return @a this
-     */
-    self_type &operator=(const UnknownValueHandler &handler);
-
-    /** Compute the default name for @a value.
-     *
-     * @param value Value without a name.
-     * @return A name for that value.
-     */
-    std::string_view operator()(E value) const;
+  /// Handle providing a default name.
+  using NameDefault = std::variant<std::monostate, std::string_view, 
UnknownValueHandler>;
+  /// Handle providing a default value.
+  using ValueDefault = std::variant<std::monostate, E, UnknownNameHandler>;
+
+  /// Visitor functor for handling @c NameDefault.
+  struct NameDefaultVisitor {
+    E _value;
+
+    std::string_view
+    operator()(std::monostate const &) const
+    {
+      throw std::domain_error(detail::what("Lexicon: invalid enumeration value 
{}", static_cast<int>(_value)).data());
+    }
 
-    /// Internal clean up, needed for assignment and destructor.
-    self_type &destroy();
+    std::string_view
+    operator()(std::string_view const &name) const
+    {
+      return name;
+    }
 
-    /// Initialize internal variant to contain nothing.
-    Content _content{Content::NIL};
-    /// Compute the required raw storage.
-    static constexpr size_t N = std::max<size_t>(sizeof(std::string_view), 
sizeof(UnknownValueHandler));
-    /// Provide raw storage for the variant.
-    char _store[N];
+    std::string_view
+    operator()(UnknownValueHandler const &handler) const
+    {
+      return handler(_value);
+    }
   };
 
-  struct ValueDefault {
-    using self_type = ValueDefault;
-
-    ValueDefault() = default;
-    ~ValueDefault();
+  /// Visitor functor for handling @c ValueDefault.
+  struct ValueDefaultVisitor {
+    std::string_view _name;
 
-    self_type &operator=(E value);
-    self_type &operator=(const UnknownNameHandler &handler);
-
-    E operator()(std::string_view name) const;
+    E
+    operator()(std::monostate const &) const
+    {
+      throw std::domain_error(detail::what("Lexicon: Unknown name \"{}\"", 
_name).data());
+    }
 
-    self_type &destroy();
+    E
+    operator()(E const &value) const
+    {
+      return value;
+    }
 
-    Content _content{Content::NIL};
-    static constexpr size_t N = std::max<size_t>(sizeof(E), 
sizeof(UnknownNameHandler));
-    char _store[N];
+    E
+    operator()(UnknownNameHandler const &handler) const
+    {
+      return handler(_name);
+    }
   };
 
   /// Each unique pair of value and name is stored as an instance of this 
class.
   /// The primary is stored first and is therefore found by normal lookup.
   struct Item {
-    Item(E, std::string_view);
+    /** Construct with a @a name and a primary @a value.
+     *
+     * @param value The primary value.
+     * @param name The name.
+     *
+     */
+    Item(E value, std::string_view name);
 
     E _value;               ///< Definition value.
     std::string_view _name; ///< Definition name
@@ -417,149 +455,32 @@ Lexicon<E>::Item::ValueLinkage::equal(E lhs, E rhs)
   return lhs == rhs;
 }
 
-// -------
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::destroy() -> self_type &
-{
-  if (_content == Content::HANDLER) {
-    reinterpret_cast<UnknownValueHandler *>(_store)->~UnknownValueHandler();
-  }
-  _content = Content::NIL;
-  return *this;
-}
-
-template <typename E> Lexicon<E>::NameDefault::~NameDefault()
-{
-  this->destroy();
-}
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::operator=(std::string_view name) -> self_type &
-{
-  this->destroy();
-  new (_store) std::string_view(name);
-  _content = Content::SCALAR;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::NameDefault::operator=(const UnknownValueHandler &handler) -> 
self_type &
-{
-  this->destroy();
-  new (_store) UnknownValueHandler(handler);
-  _content = Content::HANDLER;
-  return *this;
-}
-
-template <typename E>
-std::string_view
-Lexicon<E>::NameDefault::operator()(E value) const
-{
-  switch (_content) {
-  case Content::SCALAR:
-    return *reinterpret_cast<std::string_view const *>(_store);
-    break;
-  case Content::HANDLER:
-    return (*(reinterpret_cast<UnknownValueHandler const *>(_store)))(value);
-    break;
-  default:
-    throw std::domain_error(detail::what("Lexicon: unknown enumeration '{}'", 
uintmax_t(value)));
-    break;
-  }
-}
-
-// -------
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::destroy() -> self_type &
-{
-  if (_content == Content::HANDLER) {
-    reinterpret_cast<UnknownNameHandler *>(_store)->~UnknownNameHandler();
-  }
-  _content = Content::NIL;
-  return *this;
-}
-
-template <typename E> Lexicon<E>::ValueDefault::~ValueDefault()
-{
-  this->destroy();
-}
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::operator=(E value) -> self_type &
-{
-  this->destroy();
-  *(reinterpret_cast<E *>(_store)) = value;
-  _content                         = Content::SCALAR;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::ValueDefault::operator=(const UnknownNameHandler &handler) -> 
self_type &
-{
-  this->destroy();
-  new (_store) UnknownNameHandler(handler);
-  _content = Content::HANDLER;
-  return *this;
-}
-
-template <typename E>
-E
-Lexicon<E>::ValueDefault::operator()(std::string_view name) const
-{
-  switch (_content) {
-  case Content::SCALAR:
-    return *(reinterpret_cast<E const *>(_store));
-    break;
-  case Content::HANDLER:
-    return (*(reinterpret_cast<UnknownNameHandler const *>(_store)))(name);
-    break;
-  default:
-    throw std::domain_error(swoc::LocalBufferWriter<128>().print("Lexicon: 
unknown name '{}'\0", name).data());
-    break;
-  }
-}
 // -------
 // Lexicon
 
 template <typename E> Lexicon<E>::Lexicon() {}
 
-template <typename E> Lexicon<E>::Lexicon(const 
std::initializer_list<Definition> &items)
+template <typename E>
+Lexicon<E>::Lexicon(const std::initializer_list<Definition> &items, 
DefaultHandler handler_1, DefaultHandler handler_2)
 {
   for (auto const &item : items) {
     this->define(item.value, item.names);
   }
-}
 
-template <typename E> Lexicon<E>::Lexicon(const std::initializer_list<Pair> 
&items)
-{
-  for (auto const &item : items) {
-    this->define(item);
+  for (auto &&h : {handler_1, handler_2}) {
+    this->set_default(h);
   }
 }
 
 template <typename E>
-template <E e>
-Lexicon<E>::Lexicon(const Require<e> &, const std::array<Definition, 
static_cast<size_t>(e)> &defines)
+Lexicon<E>::Lexicon(const std::initializer_list<Pair> &items, DefaultHandler 
handler_1, DefaultHandler handler_2)
 {
-  for (auto const &def : defines) {
-    this->define(def);
+  for (auto const &item : items) {
+    this->define(item);
   }
-}
 
-template <typename E>
-template <E e>
-Lexicon<E>::Lexicon(const Require<e> &, const std::array<Pair, 
static_cast<size_t>(e)> &defines)
-{
-  for (auto const &def : defines) {
-    this->define(def);
+  for (auto &&h : {handler_1, handler_2}) {
+    this->set_default(h);
   }
 }
 
@@ -578,7 +499,7 @@ template <typename E> std::string_view 
Lexicon<E>::operator[](E value) const
   if (spot != _by_value.end()) {
     return spot->_name;
   }
-  return _name_default(value);
+  return std::visit(NameDefaultVisitor{value}, _name_default);
 }
 
 template <typename E> E Lexicon<E>::operator[](std::string_view const &name) 
const
@@ -587,7 +508,7 @@ template <typename E> E 
Lexicon<E>::operator[](std::string_view const &name) con
   if (spot != _by_name.end()) {
     return spot->_value;
   }
-  return _value_default(name);
+  return std::visit(ValueDefaultVisitor{name}, _value_default);
 }
 
 template <typename E>
@@ -636,33 +557,24 @@ Lexicon<E>::define(const Definition &init) -> self_type &
 
 template <typename E>
 auto
-Lexicon<E>::set_default(std::string_view name) -> self_type &
-{
-  _name_default = this->localize(name);
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(E value) -> self_type &
-{
-  _value_default = value;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(const UnknownValueHandler &handler) -> self_type &
-{
-  _name_default = handler;
-  return *this;
-}
-
-template <typename E>
-auto
-Lexicon<E>::set_default(const UnknownNameHandler &handler) -> self_type &
+Lexicon<E>::set_default(DefaultHandler const &handler) -> self_type &
 {
-  _value_default = handler;
+  switch (handler.index()) {
+  case 0:
+    break;
+  case 1:
+    _value_default = std::get<1>(handler);
+    break;
+  case 3:
+    _value_default = std::get<3>(handler);
+    break;
+  case 2:
+    _name_default = std::get<2>(handler);
+    break;
+  case 4:
+    _name_default = std::get<4>(handler);
+    break;
+  }
   return *this;
 }
 
diff --git a/unit_tests/test_Lexicon.cc b/unit_tests/test_Lexicon.cc
index cbc0994..db4bdde 100644
--- a/unit_tests/test_Lexicon.cc
+++ b/unit_tests/test_Lexicon.cc
@@ -66,14 +66,13 @@ TEST_CASE("Lexicon Example", "[libts][Lexicon]")
   REQUIRE(exnames[static_cast<Example>(0xBADD00D)] == "INVALID");
   REQUIRE(exnames[exnames[static_cast<Example>(0xBADD00D)]] == 
Example::INVALID);
 
-  // Case of an enumeration with a "LAST_VALUE".
-  enum class Radio { INVALID, ALPHA, BRAVO, CHARLIE, DELTA, LAST_VALUE };
+  enum class Radio { INVALID, ALPHA, BRAVO, CHARLIE, DELTA };
   using Lex = swoc::Lexicon<Radio>;
-  Lex lex(Lex::Require<Radio::LAST_VALUE>(), {{{Radio::INVALID, {"Invalid"}},
-                                               {Radio::ALPHA, {"Alpha"}},
-                                               {Radio::BRAVO, {"Bravo", 
"Beta"}},
-                                               {Radio::CHARLIE, {"Charlie"}},
-                                               {Radio::DELTA, {"Delta"}}}});
+  Lex lex({{Radio::INVALID, {"Invalid"}},
+           {Radio::ALPHA, {"Alpha"}},
+           {Radio::BRAVO, {"Bravo", "Beta"}},
+           {Radio::CHARLIE, {"Charlie"}},
+           {Radio::DELTA, {"Delta"}}});
 
   // test structured binding for iteration.
   for (auto const &[key, name] : lex) {
@@ -87,6 +86,7 @@ enum Values { NoValue, LowValue, HighValue, Priceless };
 enum Hex { A, B, C, D, E, F, INVALID };
 
 using ValueLexicon = swoc::Lexicon<Values>;
+using HexLexicon   = swoc::Lexicon<Hex>;
 
 TEST_CASE("Lexicon Constructor", "[libts][Lexicon]")
 {
@@ -162,3 +162,26 @@ TEST_CASE("Lexicon Constructor", "[libts][Lexicon]")
   }
   REQUIRE(mark.all());
 };
+
+TEST_CASE("Lexicon Constructor 2", "[libts][Lexicon]")
+{
+  // Check the various construction cases
+  // No defaults, value default, name default, both, both the other way.
+  const HexLexicon v1({{A, {"A", "ten"}}, {B, {"B", "eleven"}}});
+
+  const HexLexicon v2({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, INVALID);
+
+  const HexLexicon v3({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, "Invalid");
+
+  const HexLexicon v4({{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, "Invalid", 
INVALID);
+
+  const HexLexicon v5{{{A, {"A", "ten"}}, {B, {"B", "eleven"}}}, INVALID, 
"Invalid"};
+
+  REQUIRE(v1["a"] == A);
+  REQUIRE(v2["q"] == INVALID);
+  REQUIRE(v3[C] == "Invalid");
+  REQUIRE(v4["q"] == INVALID);
+  REQUIRE(v4[C] == "Invalid");
+  REQUIRE(v5["q"] == INVALID);
+  REQUIRE(v5[C] == "Invalid");
+}

Reply via email to