Custom Stringstream - Convert std::wstring & std::string

Custom Stringstream - Convert std::wstring & std::string

本文关键字:std string wstring Stringstream Convert Custom      更新时间:2023-10-16

如您所见,我已经得到了一个来自std::basic_stringstream<typename TString::value_type...>的模板类。问题发生在试图转换它们的时候。这可能是一个明显的问题,尽管我似乎找不到解决办法。

main为例,我有一个简单的std::wstring,并用L"123"初始化它。
在构造了std::wstring之后,调用自定义basic_stringstream类的操作符(取决于std::wstringstd::string)。

为调试目的检查WCStringStream对象,显示它包含-而不是字符串L"123",而是输入字符串的第一个元素的地址。函数to_bytesfrom_bytes确实返回正确转换后的字符串,因此剩下的唯一问题是在两个操作符函数中调用操作符:

*this << std::wstring_convert<...>().xx_bytes(s);

例子:
模板类为std::wstring
输入为std::string
正在调用&operator<<(const std::string &s)
字符串被转换。
正在调用&operator<<(const std::wstring &s)
字符串类型与模板类型匹配。
调用基类(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上运行。

首先:我认为在基类中重载格式化函数的方法是不明智的,我强烈建议不要这样做!我知道任何替代方案都需要更多的工作。

事实上,我认为你的主要问题实际上是你做没有到达你的重载函数无论如何只是显示出该方法是多么脆弱(我认为字符串描述了什么重载最终被调用,但我还没有验证这些确实是准确的,部分原因是问题中提供的代码缺乏必要的上下文):

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 operatorn");
stream << s;

由于不能更改字符串和字符串字面值的重载输出操作符,并且它们在代码转换方面做了错误的思考,我建议使用一种完全不同的方法,尽管它仍然不是没有危险(*):显式地转换字符串,尽管使用比标准提供的代码更精美的打包版本。

假设总是使用char作为所有使用的字符类型,我将使用wcvt()函数,当将所有字符串和字符串字面量插入到流中时,它将被调用。因为在函数被调用的时候,它不知道它将要使用的流的类型,它基本上会返回一个对字符序列的引用,然后将其适当地转换为用于流的字符类型。应该是这样的:

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)。这样做很容易忘记,似乎最初的目标是而不是必须记住这种转换的使用。然而,我没有看到任何替代方案比现有流系统更脆弱。完全放弃使用流并使用完全独立的格式化I/O系统可能产生更不脆弱的方法。

(*)最容易正确的方法是在程序中只使用一种字符类型,并且总是使用这种字符类型。我确实认为引入第二种字符类型wchar_t实际上是一个错误,并且通过引入char16_tchar32_t来进一步复杂化现有的混乱是一个更大的错误。如果只有一种字符类型char就好了,尽管它实际上并不表示字符,而是表示编码的字节。

问题在于显式调用基类操作符,该操作符接受const void *_Val重载并打印地址。

MyStream::operator<<(s.c_str());

问题的解决方案:

if (typeid(TString) == typeid(s))
{
    MyStream &os = *this;
    os << s.c_str();
}

当然,调用*this << s.c_str()会导致递归,但是在使用基类时,它会为正确的char类型wchar_t/char调用全局重载操作符。

另一个可行的解决方案是使用成员函数write代替操作符。