自定义 Stringstream - 转换 std::wstring & std::string
Custom Stringstream - Convert std::wstring & std::string
我有一个模板 class 来自 std::basic_stringstream<typename TString::value_type...>
,如您所见。尝试转换它们时会出现问题。这可能是一个明显的问题,尽管我似乎无法找出解决方案。
作为 main
中的示例,我有一个简单的 std::wstring
并用 L"123"
初始化它。
std::wstring
构造完成后,调用自定义basic_stringstream
的运算符class(取决于std::wstring
或std::string
)。
出于调试目的检查 WCStringStream
对象,表明它包含输入字符串的第一个元素的地址,而不是字符串 L"123"
。函数 to_bytes
和 from_bytes
执行 return 正确的转换字符串,因此唯一剩下的问题是在两个运算符函数中调用的运算符:
*this << std::wstring_convert<...>().xx_bytes(s);
示例:
模板 class 是 std::wstring
.
输入是 std::string
.
&operator<<(const std::string &s)
正在通话中。
字符串已转换。
&operator<<(const std::wstring &s)
正在通话中。
字符串类型与模板类型匹配。
base-class (basic_stringstream
) 的运算符被调用。 (或std::operator...
)
结果:
检查:{_Stringbuffer={_Seekhigh=0x007f6808 L"003BF76C췍췍췍췍췍췍췍췍췍...}...}
WCStringStream<std::wstring>::str()
-> "003BF76C"
预期结果:
"123"
这里出了什么问题?
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <codecvt>
template<class TString>
class WCStringStream : public std::basic_stringstream<typename TString::value_type,
std::char_traits<typename TString::value_type>,
std::allocator<typename TString::value_type> >
{
typedef typename TString::value_type CharTraits;
typedef std::basic_stringstream<CharTraits, std::char_traits<CharTraits>, std::allocator<CharTraits> > MyStream;
//more typedefs...
public:
//Constructor...
inline WCStringStream(void) { }
inline WCStringStream(const TString &s) : MyStream(s) { }
//and more...
//operator>> overloads...
//defines for VS2010/2015 (C++11) included
inline WCStringStream &operator<<(const std::wstring &s)
{
if (typeid(TString) == typeid(s))
MyStream::operator<<(s.c_str());
else
*this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(s);
return *this;
}
inline WCStringStream &operator<<(const std::string &s)
{
if (typeid(TString) == typeid(s))
MyStream::operator<<(s.c_str());
else
*this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(s);
return *this;
}
};
//Example main
int main(int argc, char *argv[])
{
typedef std::wstring fstring;
WCStringStream<std::wstring> ws;
WCStringStream<std::string> ss;
ws << fstring(L"123");
int a = 0;
ws >> a;
std::cout << a << std::endl;
ss << fstring(L"123");
int b = 0;
ss >> b;
std::cout << b << std::endl;
return 0;
}
我目前正在 VS2015 中编译,但我也需要在 VS2010 上 运行。
首先:我认为在基础 class 中重载格式化函数的方法是 不明智的 而我 强烈地 建议不做!我确实意识到任何替代方案都需要更多的工作。
事实上,我认为你的主要问题实际上是你 没有 达到你的重载函数,这只是表明该方法是多么脆弱(我 认为 字符串描述了最终调用的重载,但我尚未验证这些确实准确,部分原因是问题中提供的代码缺少必要的上下文):
WCStringStream<std::string> stream;
stream << "calls std::operator<< (std::ostream&, char const*)\n";
stream << L"calls std::ostream::operator<< (void const*)\n";
stream << std::string("calls std::operator<< (std::ostream&, T&&)\n";
std::string const s("calls your operator\n");
stream << s;
由于无法更改字符串和字符串文字的重载输出运算符,并且它们在代码转换方面 错误 认为,我建议使用完全不同的方法,尽管它仍然不会没有危险(*):尽管使用比标准提供的代码更好的打包版本,但显式转换字符串。
假设总是使用 char
作为所有用途的字符类型,我会使用一个函数 wcvt()
,当将它们插入流中时,它会为所有字符串和字符串文字调用。由于在调用函数时它不知道将要使用的流的类型,因此它 return 本质上是对字符序列的引用,然后将其适当地转换为字符类型用于流。那将是这样的:
template <typename cT>
class wconvert {
cT const* begin_;
cT const* end_;
public:
wconvert(std::basic_string<cT> const& s)
: begin_(s.data())
, end_(s.data() + s.size()) {
}
wconvert(cT const* s)
: begin_(s)
, end_(s + std::char_traits<cT>::length(s)) {
}
cT const* begin() const { return this->begin_; }
cT const* end() const { return this->end_; }
std::streamsize size() const { return this->end_ - this->begin_; }
};
template <typename cT>
wconvert<cT> wcvt(cT const* s) {
return wconvert<cT>(s);
}
template <typename cT>
wconvert<cT> wcvt(std::basic_string<cT> const& s) {
return wconvert<cT>(s);
}
template <typename cT>
std::basic_ostream<cT>& operator<< (std::basic_ostream<cT>& out,
wconvert<cT> const& cvt) {
return out.write(cvt.begin(), cvt.size());
}
std::ostream& operator<< (std::ostream& out, wconvert<wchar_t> const& cvt) {
auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(cvt.begin(), cvt.end());
return out.write(tmp.data(), tmp.size());
}
std::wostream& operator<< (std::wostream& out, wconvert<char> const& cvt) {
auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(cvt.begin(), cvt.end());
return out.write(tmp.data(), tmp.size());
}
当然,使用这种方法需要在 s
可能是需要转换的字符串时使用 wcvt(s)
。这样做很容易忘记,好像原来objective是为了而不是要记得用这样的转换。但是,我看不到任何对现有流系统不那么脆弱的替代方案。完全放弃使用流并使用完全独立的格式化系统 I/O 可能 产生不那么脆弱的方法。
(*) 最容易正确的方法是在程序中坚持只使用一种字符类型,总是 使用这种字符类型。我确实认为引入第二个字符类型 wchar_t
实际上是一个错误,并且通过引入 char16_t
和 char32_t
来进一步使现有的混乱更加复杂化是一个更大的错误。如果只有一种字符类型,char
,我们会好得多,尽管它实际上不代表字符,而是编码的字节。
问题是显式调用基 class 运算符,它采用 const void *_Val
重载并打印地址。
MyStream::operator<<(s.c_str());
问题解决方法:
if (typeid(TString) == typeid(s))
{
MyStream &os = *this;
os << s.c_str();
}
当然调用 *this << s.c_str()
会导致递归,但是使用基数 class,它会为正确的字符类型调用全局重载运算符 wchar_t
/ char
.
另一个可行的解决方案是使用成员函数 write
而不是运算符。
我有一个模板 class 来自 std::basic_stringstream<typename TString::value_type...>
,如您所见。尝试转换它们时会出现问题。这可能是一个明显的问题,尽管我似乎无法找出解决方案。
作为 main
中的示例,我有一个简单的 std::wstring
并用 L"123"
初始化它。
std::wstring
构造完成后,调用自定义basic_stringstream
的运算符class(取决于std::wstring
或std::string
)。
出于调试目的检查 WCStringStream
对象,表明它包含输入字符串的第一个元素的地址,而不是字符串 L"123"
。函数 to_bytes
和 from_bytes
执行 return 正确的转换字符串,因此唯一剩下的问题是在两个运算符函数中调用的运算符:
*this << std::wstring_convert<...>().xx_bytes(s);
示例:
模板 class 是 std::wstring
.
输入是 std::string
.
&operator<<(const std::string &s)
正在通话中。
字符串已转换。
&operator<<(const std::wstring &s)
正在通话中。
字符串类型与模板类型匹配。
base-class (basic_stringstream
) 的运算符被调用。 (或std::operator...
)
结果:
检查:{_Stringbuffer={_Seekhigh=0x007f6808 L"003BF76C췍췍췍췍췍췍췍췍췍...}...}
WCStringStream<std::wstring>::str()
-> "003BF76C"
预期结果:
"123"
这里出了什么问题?
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <codecvt>
template<class TString>
class WCStringStream : public std::basic_stringstream<typename TString::value_type,
std::char_traits<typename TString::value_type>,
std::allocator<typename TString::value_type> >
{
typedef typename TString::value_type CharTraits;
typedef std::basic_stringstream<CharTraits, std::char_traits<CharTraits>, std::allocator<CharTraits> > MyStream;
//more typedefs...
public:
//Constructor...
inline WCStringStream(void) { }
inline WCStringStream(const TString &s) : MyStream(s) { }
//and more...
//operator>> overloads...
//defines for VS2010/2015 (C++11) included
inline WCStringStream &operator<<(const std::wstring &s)
{
if (typeid(TString) == typeid(s))
MyStream::operator<<(s.c_str());
else
*this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(s);
return *this;
}
inline WCStringStream &operator<<(const std::string &s)
{
if (typeid(TString) == typeid(s))
MyStream::operator<<(s.c_str());
else
*this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(s);
return *this;
}
};
//Example main
int main(int argc, char *argv[])
{
typedef std::wstring fstring;
WCStringStream<std::wstring> ws;
WCStringStream<std::string> ss;
ws << fstring(L"123");
int a = 0;
ws >> a;
std::cout << a << std::endl;
ss << fstring(L"123");
int b = 0;
ss >> b;
std::cout << b << std::endl;
return 0;
}
我目前正在 VS2015 中编译,但我也需要在 VS2010 上 运行。
首先:我认为在基础 class 中重载格式化函数的方法是 不明智的 而我 强烈地 建议不做!我确实意识到任何替代方案都需要更多的工作。
事实上,我认为你的主要问题实际上是你 没有 达到你的重载函数,这只是表明该方法是多么脆弱(我 认为 字符串描述了最终调用的重载,但我尚未验证这些确实准确,部分原因是问题中提供的代码缺少必要的上下文):
WCStringStream<std::string> stream;
stream << "calls std::operator<< (std::ostream&, char const*)\n";
stream << L"calls std::ostream::operator<< (void const*)\n";
stream << std::string("calls std::operator<< (std::ostream&, T&&)\n";
std::string const s("calls your operator\n");
stream << s;
由于无法更改字符串和字符串文字的重载输出运算符,并且它们在代码转换方面 错误 认为,我建议使用完全不同的方法,尽管它仍然不会没有危险(*):尽管使用比标准提供的代码更好的打包版本,但显式转换字符串。
假设总是使用 char
作为所有用途的字符类型,我会使用一个函数 wcvt()
,当将它们插入流中时,它会为所有字符串和字符串文字调用。由于在调用函数时它不知道将要使用的流的类型,因此它 return 本质上是对字符序列的引用,然后将其适当地转换为字符类型用于流。那将是这样的:
template <typename cT>
class wconvert {
cT const* begin_;
cT const* end_;
public:
wconvert(std::basic_string<cT> const& s)
: begin_(s.data())
, end_(s.data() + s.size()) {
}
wconvert(cT const* s)
: begin_(s)
, end_(s + std::char_traits<cT>::length(s)) {
}
cT const* begin() const { return this->begin_; }
cT const* end() const { return this->end_; }
std::streamsize size() const { return this->end_ - this->begin_; }
};
template <typename cT>
wconvert<cT> wcvt(cT const* s) {
return wconvert<cT>(s);
}
template <typename cT>
wconvert<cT> wcvt(std::basic_string<cT> const& s) {
return wconvert<cT>(s);
}
template <typename cT>
std::basic_ostream<cT>& operator<< (std::basic_ostream<cT>& out,
wconvert<cT> const& cvt) {
return out.write(cvt.begin(), cvt.size());
}
std::ostream& operator<< (std::ostream& out, wconvert<wchar_t> const& cvt) {
auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(cvt.begin(), cvt.end());
return out.write(tmp.data(), tmp.size());
}
std::wostream& operator<< (std::wostream& out, wconvert<char> const& cvt) {
auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(cvt.begin(), cvt.end());
return out.write(tmp.data(), tmp.size());
}
当然,使用这种方法需要在 s
可能是需要转换的字符串时使用 wcvt(s)
。这样做很容易忘记,好像原来objective是为了而不是要记得用这样的转换。但是,我看不到任何对现有流系统不那么脆弱的替代方案。完全放弃使用流并使用完全独立的格式化系统 I/O 可能 产生不那么脆弱的方法。
(*) 最容易正确的方法是在程序中坚持只使用一种字符类型,总是 使用这种字符类型。我确实认为引入第二个字符类型 wchar_t
实际上是一个错误,并且通过引入 char16_t
和 char32_t
来进一步使现有的混乱更加复杂化是一个更大的错误。如果只有一种字符类型,char
,我们会好得多,尽管它实际上不代表字符,而是编码的字节。
问题是显式调用基 class 运算符,它采用 const void *_Val
重载并打印地址。
MyStream::operator<<(s.c_str());
问题解决方法:
if (typeid(TString) == typeid(s))
{
MyStream &os = *this;
os << s.c_str();
}
当然调用 *this << s.c_str()
会导致递归,但是使用基数 class,它会为正确的字符类型调用全局重载运算符 wchar_t
/ char
.
另一个可行的解决方案是使用成员函数 write
而不是运算符。