encoding: avoid implicit instantiation of `std::char_traits<unsigned char>`

According to the C++ standard, char_traits is not defined for unsigned
char, so don't attempt to instantiate it. This fixes building against
LLVM libc++ 19 and later, and thus with Xcode 16.3 and presumably also
on other platforms/toolchains that use libc++ by default such as Android
and FreeBSD.

Change-Id: Id0fe9c72268176a1e51d4cf2a88b9ab5d67c61d1
diff --git a/ndn-cxx/encoding/tlv.hpp b/ndn-cxx/encoding/tlv.hpp
index 43a603b..85a1398 100644
--- a/ndn-cxx/encoding/tlv.hpp
+++ b/ndn-cxx/encoding/tlv.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -280,21 +280,36 @@
  *
  * This is not a full ContiguousIterator detection implementation. It returns true for
  * the most common ContiguousIterator types used with TLV decoding function templates.
+ *
+ * @todo Replace with std::contiguous_iterator concept when we migrate to C++20.
  */
 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>;
+using IsContiguousIterator =
+#ifndef _LIBCPP_VERSION
+  std::disjunction<
+#endif
+    std::conjunction<
+      std::bool_constant<sizeof(ValueType) == 1>,
+      std::negation<std::is_same<ValueType, bool>>,
+      std::disjunction<
+        std::is_convertible<DecayedIterator, const ValueType*>,
+        std::is_convertible<DecayedIterator, typename std::vector<ValueType>::const_iterator>>>
+// ugly hack to unbreak the build with libc++ >= 19, which doesn't define char_traits<unsigned char>
+#ifndef _LIBCPP_VERSION
+    ,
+    std::conjunction<
+      std::is_same<ValueType, char>,
+      std::is_convertible<DecayedIterator, typename std::basic_string<ValueType>::const_iterator>>>
+#endif
+  ;
 
 template<typename Iterator>
 constexpr bool
 readNumber(size_t size, Iterator& begin, Iterator end, uint64_t& number) noexcept
 {
-  if constexpr (IsContiguousIterator<Iterator>) {
+  if constexpr (IsContiguousIterator<Iterator>()) {
     // fast path
     if (begin + size > end) {
       return false;
diff --git a/tests/unit/encoding/tlv.t.cpp b/tests/unit/encoding/tlv.t.cpp
index e38b5f2..be0d0df 100644
--- a/tests/unit/encoding/tlv.t.cpp
+++ b/tests/unit/encoding/tlv.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -59,9 +59,9 @@
 using StreamIterator = std::istream_iterator<uint8_t>;
 
 #define ASSERT_READ_NUMBER_IS_FAST(T) \
-  static_assert(ndn::tlv::detail::IsContiguousIterator<T>, #T " is not fast")
+  static_assert(ndn::tlv::detail::IsContiguousIterator<T>(), #T " is not fast")
 #define ASSERT_READ_NUMBER_IS_SLOW(T) \
-  static_assert(!ndn::tlv::detail::IsContiguousIterator<T>, #T " is not slow")
+  static_assert(!ndn::tlv::detail::IsContiguousIterator<T>(), #T " is not slow")
 
 ASSERT_READ_NUMBER_IS_FAST(const uint8_t*);
 ASSERT_READ_NUMBER_IS_FAST(uint8_t*);
@@ -80,6 +80,8 @@
 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(span<int8_t>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(span<char>::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);
@@ -94,6 +96,7 @@
 ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint16_t>::iterator);
 ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint32_t>::iterator);
 ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint64_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::vector<std::vector<uint8_t>>::iterator);
 ASSERT_READ_NUMBER_IS_SLOW(std::deque<uint8_t>::iterator);
 ASSERT_READ_NUMBER_IS_SLOW(std::list<uint8_t>::iterator);
 ASSERT_READ_NUMBER_IS_SLOW(StreamIterator);