encoding+lp+net: simplify with if constexpr

Change-Id: Ie2b45a502c59e5cb7181628e691f7aaaf7509d84
This commit is contained in:
Davide Pesavento committed 2023-04-28 23:21:02 -04:00
1 parent 581dd52b0d
commit 21b7f1b379
8 files changed
+147 -254

No files matched your search

+36 -45
View File
@@ -181,73 +181,64 @@ namespace detail {
* @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);
}
/**
+48 -80
View File
@@ -177,7 +177,7 @@ isCriticalType(uint32_t type) noexcept
* @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 @@ readVarNumber(Iterator& begin, Iterator end, uint64_t& number) noexcept;
* 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 @@ readType(Iterator& begin, Iterator end, uint32_t& type) noexcept;
* @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 @@ readVarNumber(Iterator& begin, Iterator end);
* 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 @@ writeVarNumber(std::ostream& os, uint64_t number);
* 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 @@ size_t
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 @@ public:
}
}
}
};
/** @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 @@ readVarNumber(Iterator& begin, Iterator end, uint64_t& number) noexcept
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 @@ readType(Iterator& begin, Iterator end, uint32_t& type) noexcept
}
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 @@ writeVarNumber(std::ostream& os, uint64_t number)
}
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"));
}
+3 -12
View File
@@ -34,33 +34,24 @@ namespace lp {
*/
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>, "");
+12 -25
View File
@@ -30,44 +30,33 @@ namespace lp {
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 @@ Block
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 @@ Packet::wireDecode(const Block& wire)
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
+26 -66
View File
@@ -47,92 +47,52 @@ namespace net {
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>
+10 -14
View File
@@ -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 @@ struct ReadVarNumberTest;
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 @@ using ReadVarNumberTests = boost::mpl::vector<
// 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), "");
+8 -8
View File
@@ -169,7 +169,7 @@ BOOST_AUTO_TEST_CASE(FromType)
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_AUTO_TEST_CASE(FromStream)
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_AUTO_TEST_CASE(FromStreamZeroLength)
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 @@ BOOST_AUTO_TEST_CASE(FromStreamPacketTooLarge)
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 @@ BOOST_AUTO_TEST_CASE(BlockFromValue)
{
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_AUTO_TEST_CASE(Parse)
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 @@ BOOST_AUTO_TEST_CASE(Parse)
};
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;
});
}
+4 -4
View File
@@ -59,11 +59,9 @@ using ArrayStream = boost::iostreams::stream<boost::iostreams::array_source>;
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::const_iterator);
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);