例如我有这门课:
template <typename T, U>
class TLVParser
{
public:
TLVParser(T value) : value_(std::move(value)) {}
void parse(const std::span<uint8_t> &buffer, size_t &offset)
{
if constexpr (std::is_arithmetic_v<T>)
{
return parsePrimitive(buffer, offset);
}
else if constexpr
constexpr(std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
{
return parseStringView(buffer, offset);
}
else if constexpr (std::is_same_v<T, std::span<U>> || std::is_same_v<T, std::vector<U>>)
{
return parseContainer(buffer, offset);
}
else
{
static_assert(always_false<T>::value, "Unsupported type for TLV parsing");
}
}
private:
T value_;
};
据我了解,
string_view
、std::string
、std::vector
和 std::span
有一些共同的属性,我可以使用相同的解析函数。那么有没有一种方法可以使用 if constexpr
来检查这种类型是否是像这样的容器,而无需为每个类型编写 ||
,而只需使用一个概念来保证该容器应该具有特定的属性,例如能够范围循环?
我的第二个问题是我可以为
std::span<uint8_t>
参数创建一个类型名称,以便我可以使用 std::vector
、std::array
甚至 uint8_t*
或 char*
吗?
正如一些人在评论中已经指出的那样,确定什么构成“容器”通常是问题所在。一种方法是检查它是否支持“std::begin()”和“std::end()”,它也处理 C 样式数组,而不是只检查具有成员“begin( )”和“end()”(这会忽略 C 样式数组)。
这是另一种技术,但我更喜欢它,因为我不会在这里讨论。它只是检查除了 C 样式数组之外,它是否是一个具有“iterator”或“const_iterator”别名(typedef)的类。宏的使用很丑陋,但它只是为了创建类型安全的代码来完成检查“iterator”或“const_iterator”的工作(该代码也可以追溯到很长一段时间并支持 C+ 之前的版本+20,尽管“_v”辅助变量仅适用于 C++17 或更高版本)。当然,现在也可以使用一些概念,因此对于那些想要实现更现代的东西的人来说,here的一些课程也可以被利用(但我不会在这里开始解决这个问题)。
无论如何,以下代码本身不符合标准,但在实践中效果很好(是的,如果需要,它会处理“std::string”,因为它具有必要的迭代器别名)。单击此处运行它。
#include <type_traits>
#include <vector>
#include <iostream>
#define DECLARE_HAS_MEMBER_TYPE(NAME) \
template <typename, typename = void> \
struct HasMemberType_##NAME : std::false_type \
{ \
}; \
template <typename T> \
struct HasMemberType_##NAME<T, std::void_t<typename T::NAME>> : std::true_type \
{ \
}; \
template <typename T> \
inline constexpr bool HasMemberType_##NAME##_v = HasMemberType_##NAME<T>::value;
DECLARE_HAS_MEMBER_TYPE(iterator)
DECLARE_HAS_MEMBER_TYPE(const_iterator)
template <typename T>
using IsContainer = std::bool_constant<HasMemberType_const_iterator_v<T> ||
HasMemberType_iterator_v<T> ||
std::is_array_v<T>>;
template <typename T>
inline constexpr bool IsContainer_v = IsContainer<T>::value;
int main()
{
std::cout << std::boolalpha << IsContainer_v<std::vector<int>> << "\n"; // true
std::cout << std::boolalpha << IsContainer_v<int []> << "\n"; // true (C-style array)
std::cout << std::boolalpha << IsContainer_v<int *> << "\n"; // false (pointer not considered a container)
return 0;
}