如何让模板函数接受任何你可以构建一些 basic_string_view 的东西
How to let a template function accept anything you can construct some basic_string_view out
我正在尝试编写一个接受所有可能的简单模板函数 basic_string_view 但我总是收到编译器错误“找不到匹配的重载函数”。
我不知道原因;调用者显式转换为 string_view 有效,但我想避免这种情况;还是故意变硬?
是否有预防这种情况的扣除指南?
有没有简单的方法将其实现为模板?
这里(以及 godbolt)是我尝试过的:
#include <string_view>
#include <string>
template <typename CharT, typename Traits> void func(std::basic_string_view<CharT, Traits> value)
//template <typename CharT> void func(std::basic_string_view<CharT> value)
//void func(std::string_view value)
{}
int main() {
std::string s;
std::string_view sv(s);
char const cs[] = "";
std::string_view csv(cs);
std::wstring ws;
std::wstring_view wsv(ws);
wchar_t const wcs[] = L"";
std::wstring_view wcsv(wcs);
func(s);
func(sv);
func(cs);
func(csv);
func(ws);
func(wsv);
func(wcs);
func(wcsv);
}
这是 msvc、clang 和 gcc 显示的错误:
error C2672: 'func': no matching overloaded function foundx64 msvc v19.latest #3
error C2783: 'void func(T)': could not deduce template argument for '<unnamed-symbol>'x64 msvc v19.latest #3
error: no matching function for call to 'func'x86-64 clang (trunk) #1
error: no matching function for call to 'func(std::string&)'x86-64 gcc (trunk) #2
编辑:
Demo Yakks c++20 答案与 Jonathan 原始字符指针支持的混合。
编译器通常不可能推导出模板参数以便转换成功。这不是 C++ 模板的设计方式。
鉴于此:
char const cs[] = "";
func(cs);
编译器必须能够回答
“我必须推断出哪些模板参数 CharT
、Traits
,以便从 const char[1]
到 std::basic_string_view<CharT,Traits>
进行适当的隐式转换?”
当然它不能这样做,至少不能不以某种方式迭代所有类型(这是无限的,可数的集合)。
很遗憾,没有模板函数的推导指南。
basic_string_view
在C++23中有一个range version of CTAD,所以在C++23中,可以使用requires
子句来约束basic_string_view{s}
为well-formed,在函数体
中借用basic_string_view
的CTAD推导出其类型
#include <string_view>
template<typename StrLike>
requires requires (const StrLike& s)
{ std::basic_string_view{s}; }
void func(const StrLike& s) {
auto sv = std::basic_string_view{s};
// use sv
}
也可以仅使用 C++20 通过检查传递给函数的参数是否是一个范围来完成此操作。然后可以使用其采用迭代器的构造函数重载将范围转换为 basic_string_view
。
我还添加了一个可以处理 char 指针的重载,因为它们不是范围。
#include <string_view>
#include <string>
#include <type_traits>
#include <ranges>
template <typename CharT, typename Traits>
void func(std::basic_string_view<CharT, Traits> value) {
// ...
}
template<typename S> requires std::ranges::contiguous_range<S>
void func(const S& s) {
func(std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)});
}
template<typename S> requires (!std::ranges::range<S> && requires (S s) { std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s); })
void func(const S& s) {
func(std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s));
}
@康桐薇的回答真棒。但是你应该避免未命名的概念。所以:
#include <string_view>
template<typename ZLike, template<class...>class Z>
concept can_become = requires (const ZLike& s) {
{ Z{s} };
};
void func(const can_become<std::basic_string_view> auto& s) {
auto sv = std::basic_string_view{s};
// use sv
}
这也可以使用 c++17 和更早的技术来解决:
template<class StrLike, std::enable_if_t<can_become_v<StrLike, std::basic_string_view>, bool> = true>
void func2(const StrLike& s) {
auto sv = std::basic_string_view{s};
// use sv
}
没有概念。
直到 c++23,std::basic_string_view
才有了 SFINAE 友好的 CTAD。没有它,您必须知道传入类型可以转换为 basic_string_view
的确切类型,然后使用该特定类型。 operator basic_string_view<X,Y>()
仅当您有要转换为可用的特定类型时才能找到。
您可以通过创建 basic_string_view
的基于范围的构造函数来解决这个问题,并手动推断所涉及的字符类型(可能还有特征)。
不支持原始字符指针的纯 c++20 解决方案:
template<typename ZLike, template<class...>class Z>
concept can_range_become = requires (const ZLike& s) {
{ Z{std::ranges::begin(s), std::ranges::end(s)} };
};
template<typename T>
concept stringlike = can_range_become<T, std::basic_string_view> && std::ranges::contiguous_range<T>;
void func(const stringlike auto& s) {
auto sv = std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)};
// use sv
}
我正在尝试编写一个接受所有可能的简单模板函数 basic_string_view 但我总是收到编译器错误“找不到匹配的重载函数”。
我不知道原因;调用者显式转换为 string_view 有效,但我想避免这种情况;还是故意变硬?
是否有预防这种情况的扣除指南?
有没有简单的方法将其实现为模板?
这里(以及 godbolt)是我尝试过的:
#include <string_view>
#include <string>
template <typename CharT, typename Traits> void func(std::basic_string_view<CharT, Traits> value)
//template <typename CharT> void func(std::basic_string_view<CharT> value)
//void func(std::string_view value)
{}
int main() {
std::string s;
std::string_view sv(s);
char const cs[] = "";
std::string_view csv(cs);
std::wstring ws;
std::wstring_view wsv(ws);
wchar_t const wcs[] = L"";
std::wstring_view wcsv(wcs);
func(s);
func(sv);
func(cs);
func(csv);
func(ws);
func(wsv);
func(wcs);
func(wcsv);
}
这是 msvc、clang 和 gcc 显示的错误:
error C2672: 'func': no matching overloaded function foundx64 msvc v19.latest #3
error C2783: 'void func(T)': could not deduce template argument for '<unnamed-symbol>'x64 msvc v19.latest #3
error: no matching function for call to 'func'x86-64 clang (trunk) #1
error: no matching function for call to 'func(std::string&)'x86-64 gcc (trunk) #2
编辑:
Demo Yakks c++20 答案与 Jonathan 原始字符指针支持的混合。
编译器通常不可能推导出模板参数以便转换成功。这不是 C++ 模板的设计方式。
鉴于此:
char const cs[] = "";
func(cs);
编译器必须能够回答
“我必须推断出哪些模板参数 CharT
、Traits
,以便从 const char[1]
到 std::basic_string_view<CharT,Traits>
进行适当的隐式转换?”
当然它不能这样做,至少不能不以某种方式迭代所有类型(这是无限的,可数的集合)。
很遗憾,没有模板函数的推导指南。
basic_string_view
在C++23中有一个range version of CTAD,所以在C++23中,可以使用requires
子句来约束basic_string_view{s}
为well-formed,在函数体
basic_string_view
的CTAD推导出其类型
#include <string_view>
template<typename StrLike>
requires requires (const StrLike& s)
{ std::basic_string_view{s}; }
void func(const StrLike& s) {
auto sv = std::basic_string_view{s};
// use sv
}
也可以仅使用 C++20 通过检查传递给函数的参数是否是一个范围来完成此操作。然后可以使用其采用迭代器的构造函数重载将范围转换为 basic_string_view
。
我还添加了一个可以处理 char 指针的重载,因为它们不是范围。
#include <string_view>
#include <string>
#include <type_traits>
#include <ranges>
template <typename CharT, typename Traits>
void func(std::basic_string_view<CharT, Traits> value) {
// ...
}
template<typename S> requires std::ranges::contiguous_range<S>
void func(const S& s) {
func(std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)});
}
template<typename S> requires (!std::ranges::range<S> && requires (S s) { std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s); })
void func(const S& s) {
func(std::basic_string_view<std::remove_cvref_t<decltype(s[0])>>(s));
}
@康桐薇的回答真棒。但是你应该避免未命名的概念。所以:
#include <string_view>
template<typename ZLike, template<class...>class Z>
concept can_become = requires (const ZLike& s) {
{ Z{s} };
};
void func(const can_become<std::basic_string_view> auto& s) {
auto sv = std::basic_string_view{s};
// use sv
}
这也可以使用 c++17 和更早的技术来解决:
template<class StrLike, std::enable_if_t<can_become_v<StrLike, std::basic_string_view>, bool> = true>
void func2(const StrLike& s) {
auto sv = std::basic_string_view{s};
// use sv
}
没有概念。
直到 c++23,std::basic_string_view
才有了 SFINAE 友好的 CTAD。没有它,您必须知道传入类型可以转换为 basic_string_view
的确切类型,然后使用该特定类型。 operator basic_string_view<X,Y>()
仅当您有要转换为可用的特定类型时才能找到。
您可以通过创建 basic_string_view
的基于范围的构造函数来解决这个问题,并手动推断所涉及的字符类型(可能还有特征)。
不支持原始字符指针的纯 c++20 解决方案:
template<typename ZLike, template<class...>class Z>
concept can_range_become = requires (const ZLike& s) {
{ Z{std::ranges::begin(s), std::ranges::end(s)} };
};
template<typename T>
concept stringlike = can_range_become<T, std::basic_string_view> && std::ranges::contiguous_range<T>;
void func(const stringlike auto& s) {
auto sv = std::basic_string_view{std::ranges::begin(s), std::ranges::end(s)};
// use sv
}