问题描述
我正在实现“ starts_with”功能,以检查字符串是否以某些前缀开头。我希望函数能够互换比较std::string
和std::string_view
。我遇到的问题是当将std::string
作为参数传递时,我希望它通过引用传递,而std::string_view
通过值传递。
当前我有此设置:
#include <string_view>
#include <utility>
template <typename String>
struct string_type {
using type = const String&;
};
template <>
struct string_type<std::string_view> {
using type = const std::string_view;
};
template <typename String>
using string_type_t = typename string_type<String>::type;
template <typename String,typename Prefix>
bool string_starts_with(string_type_t<String> str,string_type_t<Prefix> pre) {
if (pre.length() > str.length()) {
return false;
}
for (auto ch = std::pair{str.begin(),pre.begin()};
ch.second != pre.end();
++ch.first,++ch.second) {
if (*ch.first != *ch.second) {
return false;
}
}
return true;
}
int main() {
using namespace std::string_view_literals;
return string_starts_with("hello"sv,"hel"sv) ? 0 : 1;
}
但是gcc和clang(经过测试的here)无法推断出模板参数,我必须明确指定类型string_starts_with<std::string_view,std::string_view>(...,...)
。
一个显而易见的解决方案是为std::string_view
提供重载,但随后我需要用相同的主体(string_starts_with(std::string,std::string)
,string_starts_with(std::string,std::string_view)
,string_starts_with(std::string_view,std::string_view)
, string_starts_with(std::string_view,std::string)
)。这可能仍然是可管理的,但是如果要向API引入另一个类似字符串的对象,例如std::vector<char>
或std::array<char>
,那将变得难以管理。
解决方法
嵌套类型别名将禁用推论。因为编译器无法猜测嵌套参数可能在哪个类中定义。您可以定义特征模板和/或使用元编程构造(enable_if
,if constexpr
,concept/requires
,static_assert
...)来约束模板:
template<typename >
struct str_ref_traits: std::false_type{};
template<>
struct str_ref_traits<std::string&>: std::true_type{};
template<>
struct str_ref_traits<std::string_view>: std::true_type{};
template <typename S,typename P>
bool string_starts_with(S str,P pre) {
static_assert(str_ref_traits<S>::value);
static_assert(str_ref_traits<P>::value);
if (pre.length() > str.length())
return false;
return std::equal(begin(pre),end(pre),begin(str));
};
当然,如注释中所述,std::string_view
可以处理std::string
-通过设计。但是我想提一句不能推论嵌套类型别名的一般规则。
实际上,std::type_identity
的设计目标是故意用作推导禁止器。
致谢, FM。