encoding+lp+net: simplify with `if constexpr`

Change-Id: Ie2b45a502c59e5cb7181628e691f7aaaf7509d84
diff --git a/ndn-cxx/encoding/block-helpers.hpp b/ndn-cxx/encoding/block-helpers.hpp
index 653280d..9472061 100644
--- a/ndn-cxx/encoding/block-helpers.hpp
+++ b/ndn-cxx/encoding/block-helpers.hpp
@@ -181,73 +181,64 @@
  * @brief Create a binary block copying from RandomAccessIterator.
  */
 template<class Iterator>
-class BinaryBlockFast
+Block
+makeBinaryBlockFast(uint32_t type, Iterator first, Iterator last)
 {
-public:
   BOOST_CONCEPT_ASSERT((boost::RandomAccessIterator<Iterator>));
 
-  static Block
-  makeBlock(uint32_t type, Iterator first, Iterator last)
-  {
-    EncodingEstimator estimator;
-    size_t valueLength = last - first;
-    size_t totalLength = valueLength;
-    totalLength += estimator.prependVarNumber(valueLength);
-    totalLength += estimator.prependVarNumber(type);
+  EncodingEstimator estimator;
+  size_t valueLength = last - first;
+  size_t totalLength = valueLength;
+  totalLength += estimator.prependVarNumber(valueLength);
+  totalLength += estimator.prependVarNumber(type);
 
-    EncodingBuffer encoder(totalLength, 0);
-    encoder.prependRange(first, last);
-    encoder.prependVarNumber(valueLength);
-    encoder.prependVarNumber(type);
+  EncodingBuffer encoder(totalLength, 0);
+  encoder.prependRange(first, last);
+  encoder.prependVarNumber(valueLength);
+  encoder.prependVarNumber(type);
 
-    return encoder.block();
-  }
-};
+  return encoder.block();
+}
 
 /**
  * @brief Create a binary block copying from generic InputIterator.
  */
 template<class Iterator>
-class BinaryBlockSlow
+Block
+makeBinaryBlockSlow(uint32_t type, Iterator first, Iterator last)
 {
-public:
   BOOST_CONCEPT_ASSERT((boost::InputIterator<Iterator>));
 
-  static Block
-  makeBlock(uint32_t type, Iterator first, Iterator last)
-  {
-    // reserve 4 bytes in front (common for 1(type)-3(length) encoding
-    // Actual size will be adjusted as necessary by the encoder
-    EncodingBuffer encoder(4, 4);
-    size_t valueLength = encoder.appendRange(first, last);
-    encoder.prependVarNumber(valueLength);
-    encoder.prependVarNumber(type);
+  // Reserve 4 bytes in front, common for 1(type)-3(length) encoding.
+  // Actual size will be adjusted as necessary by the encoder.
+  EncodingBuffer encoder(4, 4);
+  size_t valueLength = encoder.appendRange(first, last);
+  encoder.prependVarNumber(valueLength);
+  encoder.prependVarNumber(type);
 
-    return encoder.block();
-  }
-};
+  return encoder.block();
+}
 
 } // namespace detail
 
-/** @brief Create a TLV block copying TLV-VALUE from iterators.
- *  @tparam Iterator an InputIterator dereferenceable to a 1-octet type; a faster implementation is
- *                   automatically selected for RandomAccessIterator
- *  @param type TLV-TYPE number
- *  @param first begin iterator
- *  @param last past-the-end iterator
- *  @sa prependBinaryBlock
+/**
+ * @brief Create a TLV block copying TLV-VALUE from iterators.
+ * @tparam Iterator an InputIterator dereferenceable to a 1-octet type; a faster implementation is
+ *                  automatically selected for RandomAccessIterator
+ * @param type TLV-TYPE number
+ * @param first begin iterator
+ * @param last past-the-end iterator
+ * @sa prependBinaryBlock
  */
 template<class Iterator>
 Block
 makeBinaryBlock(uint32_t type, Iterator first, Iterator last)
 {
-  using BinaryBlockHelper = std::conditional_t<
-    std::is_base_of_v<std::random_access_iterator_tag,
-                      typename std::iterator_traits<Iterator>::iterator_category>,
-    detail::BinaryBlockFast<Iterator>,
-    detail::BinaryBlockSlow<Iterator>>;
-
-  return BinaryBlockHelper::makeBlock(type, first, last);
+  if constexpr (std::is_base_of_v<std::random_access_iterator_tag,
+                                  typename std::iterator_traits<Iterator>::iterator_category>)
+    return detail::makeBinaryBlockFast(type, first, last);
+  else
+    return detail::makeBinaryBlockSlow(type, first, last);
 }
 
 /**
diff --git a/ndn-cxx/encoding/tlv.hpp b/ndn-cxx/encoding/tlv.hpp
index 6480538..12bc854 100644
--- a/ndn-cxx/encoding/tlv.hpp
+++ b/ndn-cxx/encoding/tlv.hpp
@@ -177,7 +177,7 @@
  * @return true if number was successfully read from input, false otherwise
  */
 template<typename Iterator>
-[[nodiscard]] bool
+[[nodiscard]] constexpr bool
 readVarNumber(Iterator& begin, Iterator end, uint64_t& number) noexcept;
 
 /**
@@ -194,7 +194,7 @@
  *       the TLV-TYPE is zero or larger than 2^32-1 (maximum allowed by the packet format).
  */
 template<typename Iterator>
-[[nodiscard]] bool
+[[nodiscard]] constexpr bool
 readType(Iterator& begin, Iterator end, uint32_t& type) noexcept;
 
 /**
@@ -208,7 +208,7 @@
  * @throw tlv::Error VAR-NUMBER cannot be read
  */
 template<typename Iterator>
-uint64_t
+constexpr uint64_t
 readVarNumber(Iterator& begin, Iterator end);
 
 /**
@@ -224,13 +224,13 @@
  *       the TLV-TYPE is zero or larger than 2^32-1 (maximum allowed by the packet format).
  */
 template<typename Iterator>
-uint32_t
+constexpr uint32_t
 readType(Iterator& begin, Iterator end);
 
 /**
  * @brief Get the number of bytes necessary to hold the value of @p number encoded as VAR-NUMBER.
  */
-constexpr size_t
+[[nodiscard]] constexpr size_t
 sizeOfVarNumber(uint64_t number) noexcept;
 
 /**
@@ -254,13 +254,13 @@
  *       If \p size differs from `std::distance(begin, end)`, tlv::Error exception will be thrown.
  */
 template<typename Iterator>
-uint64_t
+constexpr uint64_t
 readNonNegativeInteger(size_t size, Iterator& begin, Iterator end);
 
 /**
  * @brief Get the number of bytes necessary to hold the value of @p integer encoded as NonNegativeInteger.
  */
-constexpr size_t
+[[nodiscard]] constexpr size_t
 sizeOfNonNegativeInteger(uint64_t integer) noexcept;
 
 /**
@@ -271,48 +271,35 @@
 writeNonNegativeInteger(std::ostream& os, uint64_t integer);
 
 /////////////////////////////////////////////////////////////////////////////////
-/////////////////////////////////////////////////////////////////////////////////
-/////////////////////////////////////////////////////////////////////////////////
-
 // Inline definitions
-
-/////////////////////////////////////////////////////////////////////////////////
-/////////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////////
 
 namespace detail {
 
-/** @brief Function object to read a number from InputIterator.
+/**
+ * @brief Determine whether to select the readNumber() implementation for ContiguousIterator.
+ *
+ * This is not a full ContiguousIterator detection implementation. It returns true for
+ * the most common ContiguousIterator types used with TLV decoding function templates.
  */
-template<typename Iterator>
-class ReadNumberSlow
-{
-public:
-  constexpr bool
-  operator()(size_t size, Iterator& begin, Iterator end, uint64_t& number) const noexcept
-  {
-    number = 0;
-    size_t count = 0;
-    for (; begin != end && count < size; ++begin, ++count) {
-      number = (number << 8) | *begin;
-    }
-    return count == size;
-  }
-};
+template<typename Iterator,
+         typename DecayedIterator = std::decay_t<Iterator>,
+         typename ValueType = typename std::iterator_traits<DecayedIterator>::value_type>
+inline constexpr bool IsContiguousIterator =
+  (std::is_convertible_v<DecayedIterator, const ValueType*> ||
+   std::is_convertible_v<DecayedIterator, typename std::basic_string<ValueType>::const_iterator> ||
+   std::is_convertible_v<DecayedIterator, typename std::vector<ValueType>::const_iterator>) &&
+  sizeof(ValueType) == 1 && !std::is_same_v<ValueType, bool>;
 
-/** @brief Function object to read a number from ContiguousIterator.
- */
 template<typename Iterator>
-class ReadNumberFast
+constexpr bool
+readNumber(size_t size, Iterator& begin, Iterator end, uint64_t& number) noexcept
 {
-public:
-  constexpr bool
-  operator()(size_t size, Iterator& begin, Iterator end, uint64_t& number) const noexcept
-  {
+  if constexpr (IsContiguousIterator<Iterator>) {
+    // fast path
     if (begin + size > end) {
       return false;
     }
-
     switch (size) {
       case 1: {
         number = *begin;
@@ -345,36 +332,21 @@
       }
     }
   }
-};
-
-/** @brief Determine whether to select ReadNumber implementation for ContiguousIterator.
- *
- *  This is not a full ContiguousIterator detection implementation. It returns true for the most
- *  common ContiguousIterator types used with TLV decoding function templates.
- */
-template<typename Iterator,
-         typename DecayedIterator = std::decay_t<Iterator>,
-         typename ValueType = typename std::iterator_traits<DecayedIterator>::value_type>
-constexpr bool
-shouldSelectContiguousReadNumber()
-{
-  return (std::is_convertible_v<DecayedIterator, const ValueType*> ||
-          std::is_convertible_v<DecayedIterator, typename std::basic_string<ValueType>::const_iterator> ||
-          std::is_convertible_v<DecayedIterator, typename std::vector<ValueType>::const_iterator>) &&
-         sizeof(ValueType) == 1 &&
-         !std::is_same_v<ValueType, bool>;
+  else {
+    // slow path
+    number = 0;
+    size_t count = 0;
+    for (; begin != end && count < size; ++begin, ++count) {
+      number = (number << 8) | *begin;
+    }
+    return count == size;
+  }
 }
 
-template<typename Iterator>
-class ReadNumber : public std::conditional_t<shouldSelectContiguousReadNumber<Iterator>(),
-                                             ReadNumberFast<Iterator>, ReadNumberSlow<Iterator>>
-{
-};
-
 } // namespace detail
 
 template<typename Iterator>
-bool
+constexpr bool
 readVarNumber(Iterator& begin, Iterator end, uint64_t& number) noexcept
 {
   if (begin == end)
@@ -387,13 +359,13 @@
     return true;
   }
 
-  size_t size = firstOctet == 253 ? 2 :
-                firstOctet == 254 ? 4 : 8;
-  return detail::ReadNumber<Iterator>()(size, begin, end, number);
+  size_t len = firstOctet == 253 ? 2 :
+               firstOctet == 254 ? 4 : 8;
+  return detail::readNumber(len, begin, end, number);
 }
 
 template<typename Iterator>
-bool
+constexpr bool
 readType(Iterator& begin, Iterator end, uint32_t& type) noexcept
 {
   uint64_t number = 0;
@@ -407,24 +379,20 @@
 }
 
 template<typename Iterator>
-uint64_t
+constexpr uint64_t
 readVarNumber(Iterator& begin, Iterator end)
 {
-  if (begin == end) {
-    NDN_THROW(Error("Empty buffer during TLV parsing"));
-  }
-
-  uint64_t value = 0;
-  bool isOk = readVarNumber(begin, end, value);
+  uint64_t number = 0;
+  bool isOk = readVarNumber(begin, end, number);
   if (!isOk) {
     NDN_THROW(Error("Insufficient data during TLV parsing"));
   }
 
-  return value;
+  return number;
 }
 
 template<typename Iterator>
-uint32_t
+constexpr uint32_t
 readType(Iterator& begin, Iterator end)
 {
   uint64_t type = readVarNumber(begin, end);
@@ -471,15 +439,15 @@
 }
 
 template<typename Iterator>
-uint64_t
-readNonNegativeInteger(size_t size, Iterator& begin, Iterator end)
+constexpr uint64_t
+readNonNegativeInteger(size_t len, Iterator& begin, Iterator end)
 {
-  if (size != 1 && size != 2 && size != 4 && size != 8) {
-    NDN_THROW(Error("Invalid length " + to_string(size) + " for NonNegativeInteger"));
+  if (len != 1 && len != 2 && len != 4 && len != 8) {
+    NDN_THROW(Error("Invalid NonNegativeInteger length " + to_string(len)));
   }
 
   uint64_t number = 0;
-  bool isOk = detail::ReadNumber<Iterator>()(size, begin, end, number);
+  bool isOk = detail::readNumber(len, begin, end, number);
   if (!isOk) {
     NDN_THROW(Error("Insufficient data during NonNegativeInteger parsing"));
   }
diff --git a/ndn-cxx/lp/field.hpp b/ndn-cxx/lp/field.hpp
index cae13f7..3ede2b5 100644
--- a/ndn-cxx/lp/field.hpp
+++ b/ndn-cxx/lp/field.hpp
@@ -34,33 +34,24 @@
  */
 namespace field_location_tags {
 
-class Base
-{
-};
-
 /**
  * \brief A header field.
  */
-class Header : public Base
-{
-};
+struct Header {};
 
 /**
  * \brief The Fragment field.
  */
-class Fragment : public Base
-{
-};
+struct Fragment {};
 
 } // namespace field_location_tags
 
 /**
  * \brief Concept check for NDNLPv2 fields.
  */
-template<class X>
+template<typename X>
 struct Field
 {
-  static_assert(std::is_base_of_v<field_location_tags::Base, typename X::FieldLocation>, "");
   static_assert(std::is_same_v<typename X::TlvType::value_type, uint32_t>, "");
   static_assert(std::is_same_v<typename X::IsRepeatable::value_type, bool>, "");
   static_assert(std::is_default_constructible_v<typename X::ValueType>, "");
diff --git a/ndn-cxx/lp/packet.cpp b/ndn-cxx/lp/packet.cpp
index 7097384..e942acc 100644
--- a/ndn-cxx/lp/packet.cpp
+++ b/ndn-cxx/lp/packet.cpp
@@ -30,44 +30,33 @@
 
 namespace {
 
-template<typename TAG>
-int
-getLocationSortOrder() noexcept;
-
-template<>
-constexpr int
-getLocationSortOrder<field_location_tags::Header>() noexcept
+template<typename Tag>
+constexpr int8_t
+getLocationSortOrder() noexcept
 {
-  return 1;
+  if constexpr (std::is_same_v<Tag, field_location_tags::Header>)
+    return 1;
+  if constexpr (std::is_same_v<Tag, field_location_tags::Fragment>)
+    return 2;
 }
 
-template<>
-constexpr int
-getLocationSortOrder<field_location_tags::Fragment>() noexcept
+struct FieldInfo
 {
-  return 2;
-}
-
-class FieldInfo
-{
-public:
   constexpr
   FieldInfo() noexcept = default;
 
   explicit
   FieldInfo(uint32_t type) noexcept;
 
-public:
   uint32_t tlvType = 0;       ///< TLV-TYPE of the field; 0 if field does not exist
   bool isRecognized = false;  ///< is this field known
   bool canIgnore = false;     ///< can this unknown field be ignored
   bool isRepeatable = false;  ///< is the field repeatable
-  int locationSortOrder = getLocationSortOrder<field_location_tags::Header>(); ///< sort order of field_location_tag
+  int8_t locationSortOrder = getLocationSortOrder<field_location_tags::Header>(); ///< sort order of field_location_tag
 };
 
-class ExtractFieldInfo
+struct ExtractFieldInfo
 {
-public:
   using result_type = void;
 
   template<typename T>
@@ -115,7 +104,7 @@
 Packet::wireEncode() const
 {
   // If no header or trailer, return bare network packet
-  Block::element_container elements = m_wire.elements();
+  auto elements = m_wire.elements();
   if (elements.size() == 1 && elements.front().type() == FragmentField::TlvType::value) {
     elements.front().parse();
     return elements.front().elements().front();
@@ -169,9 +158,7 @@
 bool
 Packet::comparePos(uint32_t first, const Block& second) noexcept
 {
-  FieldInfo firstInfo(first);
-  FieldInfo secondInfo(second.type());
-  return compareFieldSortOrder(firstInfo, secondInfo);
+  return compareFieldSortOrder(FieldInfo(first), FieldInfo(second.type()));
 }
 
 } // namespace lp
diff --git a/ndn-cxx/net/impl/netlink-message.hpp b/ndn-cxx/net/impl/netlink-message.hpp
index 01d5282..5eb44b8 100644
--- a/ndn-cxx/net/impl/netlink-message.hpp
+++ b/ndn-cxx/net/impl/netlink-message.hpp
@@ -47,92 +47,52 @@
 
 template<typename T>
 constexpr size_t
-getAttributeLength(const T* attr);
-
-template<>
-constexpr size_t
-getAttributeLength(const nlattr* attr)
+getAttributeLength(const T* attr)
 {
-  return attr->nla_len;
-}
-
-template<>
-constexpr size_t
-getAttributeLength(const rtattr* attr)
-{
-  return attr->rta_len;
+  if constexpr (std::is_same_v<T, nlattr>)
+    return attr->nla_len;
+  if constexpr (std::is_same_v<T, rtattr>)
+    return attr->rta_len;
 }
 
 template<typename T>
 constexpr size_t
-getAttributeLengthAligned(const T* attr);
-
-template<>
-constexpr size_t
-getAttributeLengthAligned(const nlattr* attr)
+getAttributeLengthAligned(const T* attr)
 {
-  return NLA_ALIGN(attr->nla_len);
-}
-
-template<>
-constexpr size_t
-getAttributeLengthAligned(const rtattr* attr)
-{
-  return RTA_ALIGN(attr->rta_len);
+  if constexpr (std::is_same_v<T, nlattr>)
+    return NLA_ALIGN(attr->nla_len);
+  if constexpr (std::is_same_v<T, rtattr>)
+    return RTA_ALIGN(attr->rta_len);
 }
 
 template<typename T>
-constexpr uint16_t
-getAttributeType(const T* attr);
-
-template<>
-constexpr uint16_t
-getAttributeType(const nlattr* attr)
+uint16_t
+getAttributeType(const T* attr)
 {
-  return attr->nla_type & NLA_TYPE_MASK;
-}
-
-template<>
-constexpr uint16_t
-getAttributeType(const rtattr* attr)
-{
-  return attr->rta_type;
+  if constexpr (std::is_same_v<T, nlattr>)
+    return attr->nla_type & NLA_TYPE_MASK;
+  if constexpr (std::is_same_v<T, rtattr>)
+    return attr->rta_type;
 }
 
 template<typename T>
 const uint8_t*
-getAttributeValue(const T* attr);
-
-template<>
-inline const uint8_t*
-getAttributeValue(const nlattr* attr)
+getAttributeValue(const T* attr)
 {
-  return reinterpret_cast<const uint8_t*>(attr) + NLA_HDRLEN;
-}
-
-template<>
-inline const uint8_t*
-getAttributeValue(const rtattr* attr)
-{
-  return reinterpret_cast<const uint8_t*>(RTA_DATA(const_cast<rtattr*>(attr)));
+  if constexpr (std::is_same_v<T, nlattr>)
+    return reinterpret_cast<const uint8_t*>(attr) + NLA_HDRLEN;
+  if constexpr (std::is_same_v<T, rtattr>)
+    return reinterpret_cast<const uint8_t*>(RTA_DATA(const_cast<rtattr*>(attr)));
 }
 
 template<typename T>
 constexpr size_t
-getAttributeValueLength(const T* attr);
-
-template<>
-constexpr size_t
-getAttributeValueLength(const nlattr* attr)
+getAttributeValueLength(const T* attr)
 {
-  return attr->nla_len - NLA_HDRLEN;
-}
-
-template<>
-constexpr size_t
-getAttributeValueLength(const rtattr* attr)
-{
-  return RTA_PAYLOAD(attr);
+  if constexpr (std::is_same_v<T, nlattr>)
+    return attr->nla_len - NLA_HDRLEN;
+  if constexpr (std::is_same_v<T, rtattr>)
+    return RTA_PAYLOAD(attr);
 }
 
 template<typename T>
diff --git a/tests/benchmarks/encoding-bench.cpp b/tests/benchmarks/encoding-bench.cpp
index 3ab246d..1adb452 100644
--- a/tests/benchmarks/encoding-bench.cpp
+++ b/tests/benchmarks/encoding-bench.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2020 Regents of the University of California.
+ * Copyright (c) 2013-2023 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -42,34 +42,30 @@
 template<>
 struct ReadVarNumberTest<1>
 {
-  static const uint8_t WIRE[];
-  static const uint64_t VALUE = 252;
+  static constexpr uint8_t WIRE[] = {0xfc};
+  static constexpr uint64_t VALUE = 252;
 };
-const uint8_t ReadVarNumberTest<1>::WIRE[] = {0xfc};
 
 template<>
 struct ReadVarNumberTest<3>
 {
-  static const uint8_t WIRE[];
-  static const uint64_t VALUE = 253;
+  static constexpr uint8_t WIRE[] = {0xfd, 0x00, 0xfd};
+  static constexpr uint64_t VALUE = 253;
 };
-const uint8_t ReadVarNumberTest<3>::WIRE[] = {0xfd, 0x00, 0xfd};
 
 template<>
 struct ReadVarNumberTest<5>
 {
-  static const uint8_t WIRE[];
-  static const uint64_t VALUE = 65536;
+  static constexpr uint8_t WIRE[] = {0xfe, 0x00, 0x01, 0x00, 0x00};
+  static constexpr uint64_t VALUE = 65536;
 };
-const uint8_t ReadVarNumberTest<5>::WIRE[] = {0xfe, 0x00, 0x01, 0x00, 0x00};
 
 template<>
 struct ReadVarNumberTest<9>
 {
-  static const uint8_t WIRE[];
-  static const uint64_t VALUE = 4294967296;
+  static constexpr uint8_t WIRE[] = {0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
+  static constexpr uint64_t VALUE = 4294967296;
 };
-const uint8_t ReadVarNumberTest<9>::WIRE[] = {0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
 
 template<size_t WIRE_SIZE, size_t ALIGNMENT_OFFSET>
 struct ReadVarNumberAlignTest : public ReadVarNumberTest<WIRE_SIZE>
@@ -102,7 +98,7 @@
 // It is recommended to run the benchmark multiple times and take the average.
 BOOST_AUTO_TEST_CASE_TEMPLATE(ReadVarNumber, Test, ReadVarNumberTests)
 {
-  const int N_ITERATIONS = 100000000;
+  constexpr int N_ITERATIONS = 100000000;
 
   alignas(8) uint8_t buffer[16];
   static_assert(Test::AlignmentOffset::value + sizeof(Test::WIRE) <= sizeof(buffer), "");
diff --git a/tests/unit/encoding/block.t.cpp b/tests/unit/encoding/block.t.cpp
index f75eaf0..8dd21d3 100644
--- a/tests/unit/encoding/block.t.cpp
+++ b/tests/unit/encoding/block.t.cpp
@@ -169,7 +169,7 @@
   BOOST_CHECK_EQUAL(b3.isValid(), false);
   BOOST_CHECK_EQUAL(b3.type(), tlv::Invalid);
   BOOST_CHECK_EXCEPTION(b3.size(), Block::Error, [] (const auto& e) {
-    return e.what() == "Cannot determine size of invalid block"s;
+    return e.what() == "Cannot determine size of invalid block"sv;
   });
   BOOST_CHECK_EQUAL(b3.hasValue(), false);
   BOOST_CHECK_EQUAL(b3.value_size(), 0);
@@ -253,7 +253,7 @@
 
   BOOST_CHECK(stream.eof());
   BOOST_CHECK_EXCEPTION(Block::fromStream(stream), tlv::Error, [] (const auto& e) {
-    return e.what() == "Empty buffer during TLV parsing"s;
+    return e.what() == "Insufficient data during TLV parsing"sv;
   });
 }
 
@@ -315,7 +315,7 @@
   BOOST_CHECK(b3.value() == nullptr);
 
   BOOST_CHECK_EXCEPTION(Block::fromStream(stream), tlv::Error, [] (const auto& e) {
-    return e.what() == "Empty buffer during TLV parsing"s;
+    return e.what() == "Insufficient data during TLV parsing"sv;
   });
 }
 
@@ -331,7 +331,7 @@
   stream.seekg(0);
 
   BOOST_CHECK_EXCEPTION(Block::fromStream(stream), tlv::Error, [] (const auto& e) {
-    return e.what() == "TLV-LENGTH from stream exceeds limit"s;
+    return e.what() == "TLV-LENGTH from stream exceeds limit"sv;
   });
 }
 
@@ -447,12 +447,12 @@
 {
   Block b1(301);
   BOOST_CHECK_EXCEPTION(b1.blockFromValue(), Block::Error, [] (const auto& e) {
-    return e.what() == "Cannot construct block from empty TLV-VALUE"s;
+    return e.what() == "Cannot construct block from empty TLV-VALUE"sv;
   });
 
   Block b2(302, std::make_shared<Buffer>());
   BOOST_CHECK_EXCEPTION(b2.blockFromValue(), Block::Error, [] (const auto& e) {
-    return e.what() == "Cannot construct block from empty TLV-VALUE"s;
+    return e.what() == "Cannot construct block from empty TLV-VALUE"sv;
   });
 
   b1.encode();
@@ -491,7 +491,7 @@
 
   BOOST_CHECK(data.get(0x15) == data.elements().at(2));
   BOOST_CHECK_EXCEPTION(data.get(0x01), Block::Error, [] (const auto& e) {
-    return e.what() == "No sub-element of type 1 found in block of type 6"s;
+    return e.what() == "No sub-element of type 1 found in block of type 6"sv;
   });
 
   BOOST_CHECK(data.find(0x15) == data.elements_begin() + 2);
@@ -503,7 +503,7 @@
   };
   Block bad(MALFORMED);
   BOOST_CHECK_EXCEPTION(bad.parse(), Block::Error, [] (const auto& e) {
-    return e.what() == "TLV-LENGTH of sub-element of type 7 exceeds TLV-VALUE boundary of parent block"s;
+    return e.what() == "TLV-LENGTH of sub-element of type 7 exceeds TLV-VALUE boundary of parent block"sv;
   });
 }
 
diff --git a/tests/unit/encoding/tlv.t.cpp b/tests/unit/encoding/tlv.t.cpp
index 5eaa17a..9cd7cc4 100644
--- a/tests/unit/encoding/tlv.t.cpp
+++ b/tests/unit/encoding/tlv.t.cpp
@@ -59,11 +59,9 @@
 using StreamIterator = std::istream_iterator<uint8_t>;
 
 #define ASSERT_READ_NUMBER_IS_FAST(T) \
-  static_assert(std::is_base_of_v<detail::ReadNumberFast<T>, detail::ReadNumber<T>>, \
-                # T " should use ReadNumberFast")
+  static_assert(detail::IsContiguousIterator<T>, #T " is not fast")
 #define ASSERT_READ_NUMBER_IS_SLOW(T) \
-  static_assert(std::is_base_of_v<detail::ReadNumberSlow<T>, detail::ReadNumber<T>>, \
-                # T " should use ReadNumberSlow")
+  static_assert(!detail::IsContiguousIterator<T>, #T " is not slow")
 
 ASSERT_READ_NUMBER_IS_FAST(const uint8_t*);
 ASSERT_READ_NUMBER_IS_FAST(uint8_t*);
@@ -80,6 +78,8 @@
 ASSERT_READ_NUMBER_IS_FAST(Uint8Array::iterator);
 using CharArray = std::array<char, 87>;
 ASSERT_READ_NUMBER_IS_FAST(CharArray::iterator);
+ASSERT_READ_NUMBER_IS_FAST(span<const uint8_t>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(span<uint8_t>::iterator);
 ASSERT_READ_NUMBER_IS_FAST(std::string::const_iterator);
 ASSERT_READ_NUMBER_IS_FAST(std::string::iterator);
 ASSERT_READ_NUMBER_IS_FAST(Buffer::const_iterator);