我有许多用户定义的类,我想为其定义一个 to_string 函数。然而,我不想只为
std::string
定义它,而是为 std::basic_string
的所有可能实例定义它。因此,这是一个天真的尝试,但失败了:
我们有一些类型,并且我们为每个类型定义了
to_basic_string()
函数。然后我们有更具体的 to_string()
、to_wstring()
、to_u8string()
,它们围绕每个 to_basic_string()
实例化的 basic_string
。使用通用参数,因此我们不必为每个用户定义的类型定义相同的东西。
#include <string>
#include <iostream>
#include <sstream>
using namespace std::literals;
// Define the types...
template <std::size_t... Is>
struct foo {};
struct bar {};
struct baz {};
// ...
// Define the generic to_basic_string functions...
template<typename char_type, std::size_t first, std::size_t... Is>
std::basic_string<char_type> to_basic_string(foo<first, Is...> arg)
{
std::basic_stringstream<char_type> stst;
stst << "foo<"s << first;
if (sizeof...(Is) > 0)
{
((stst << ","s << Is), ...);
}
stst << ">"s;
return stst.str();
}
template<typename char_type>
std::basic_string<char_type> to_basic_string(bar arg)
{
using namespace std::literals;
return "bar"s;
}
template<typename char_type>
std::basic_string<char_type> to_basic_string(baz arg)
{
using namespace std::literals;
return std::basic_string<char_type>{};
}
// ...
// Define (for generic argument) functions that convert to the particular basic_string instantiations, such as string, wstring, etc.
std::string to_string(auto arg)
{
return to_basic_string<char>(arg);
}
std::wstring to_wstring(auto arg)
{
return to_basic_string<wchar_t>(arg);
}
std::u8string to_u8string(auto arg)
{
return to_basic_string<char8_t>(arg);
}
// ...
int main()
{
std::cout << to_string(foo<0, 2, 4, 6, 8>{}) << "\n"s;
std::cout << to_string(bar{}) << "\n"s;
std::cout << to_string(baz{}) << "\n"s;
// std::wcout << to_wstring(foo<0, 2, 4, 6, 8>{}) << L"\n"s;
// std::wcout << to_wstring(bar{}) << L"\n"s;
// std::wcout << to_wstring(baz{}) << L"\n"s;
}
将其用于
to_string()
时,一切似乎工作正常。对于 to_wstring()
和其他函数,它无法编译,这并不奇怪:to_basic_string()
函数都使用 basic_string
文字,例如 "foo<"s
,仅适用于基于 char
的文字。
这就提出了一个问题:如何让这个函数支持各种
basic_string
类型,而不必有效地复制函数逻辑?请注意,由于示例较少,所提供的函数具有相当简单的实现,因此并不反映其实际的对应函数。但是,它们确实反映了涉及的一般元素:始终使用字符串文字,并且使用字符串流来连接元素。
至于 c++ 标准的选择,对于这个问题来说,任何达到 c++20 的标准都是公平的。
(顺便说一句,考虑到标准中对它们的支持是多么稀疏,我质疑尝试提供
basic_string
和 string
之外的支持是否有很大用处。有wstring
和std::cout
,但是据我所知,std::wcout
、u8string
和u16string
没有这样的等价物。)这些模板函数将从一些特定函数中调用,每种 basic_string 调用一个函数(但对于所有类都是通用的,使用
u32string
参数)。
实际上,使用auto
参数的函数是“缩写函数模板”,最终您将得到两层嵌套的模板函数。有帮助吗?
无论如何,您面临两个(相关的)问题。
auto
模板函数应该返回任何类型的 basic_string,但它们实际上被设计为始终返回“经典”基于字符的字符串(即
to_basic_string()
的实例)。
std::basic_string<char>
和
string
、wstring
等)之间进行转换。
u8string
、
char
或 wchar_t
这样的类型指定了每个字符/实体的 size,但它们没有提及任何关于 encoding 的内容,这主要取决于实现(并且与“区域设置”等)。事实上,源代码中 char/wchar 文字的编码也不是由标准定义的。 单个
char8_t
字符串通常以某种 ISO-8859 变体(每个字符/字节一个字符)进行编码(或者,也许以 UTF-8 编码,每个字符/实体使用一个或多个字符/字节)。宽
char
wstring 可以使用 UCS-2(一种旧的 UTF-16 限制形式)进行编码。或者不是。为了解决第一个问题,我会将内部级别模板函数替换为简单函数(除了部分 foo 之外)并执行以下操作:
wchar_t
正如我所说,第二个问题更复杂,您可能无法找到真正通用的解决方案。这里提供的只是一个示例。
最后一点,我将移动内部函数,例如特定类中的
#include <string>
#include <iostream>
#include <sstream>
using namespace std::literals;
// Define the types...
template <std::size_t... Is>
struct foo {};
struct bar {};
struct baz {};
// Define the to_string functions for each class
template<typename std::size_t first, std::size_t... Is>
std::string to_string(foo<first, Is...> arg)
{
std::stringstream stst;
stst << "foo<"s << first;
if (sizeof...(Is) > 0)
{
((stst << ","s << Is), ...);
}
stst << ">"s;
return stst.str();
}
std::string to_string(bar arg)
{
using namespace std::literals;
return "bar"s;
}
std::string to_string(baz arg)
{
using namespace std::literals;
return std::string{};
}
// Define (for generic argument) functions that convert to the particular basic_string instantiations, such as string, wstring, etc.
#include <codecvt>
std::wstring to_wstring(auto arg)
{
// This should work if the (char) string is UTF-8 encoded
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(to_string(arg));
}
std::u8string to_u8string(auto arg)
{
return to_string(arg);
}
int main()
{
std::cout << to_string(foo<0, 2, 4, 6, 8>{}) << "\n"s;
std::cout << to_string(bar{}) << "\n"s;
std::cout << to_string(baz{}) << "\n"s;
std::wcout << to_wstring(foo<0, 2, 4, 6, 8>{}) << L"\n"s;
std::wcout << to_wstring(bar{}) << L"\n"s;
std::wcout << to_wstring(baz{}) << L"\n"s;
}
,删除参数。