我怎样才能使 Unicode iostream i/o 在 Windows 和 Unix-land 中工作?
How can I make Unicode iostream i/o work in both Windows and Unix-land?
注意:这是 question-with-answer 的目的是为了记录其他人可能会觉得有用的技术,并且可能会意识到其他人甚至更好的解决方案。请随时添加评论或问题作为评论。也请随时添加其他答案。 :)
问题 #1:
- 通过流 对 Unicode 的控制台支持在 Windows API 级别受到严重限制。可用于普通桌面应用程序的唯一相关代码页是 65001,UTF-8。然后交互式输入在 API 级别失败,甚至非 ASCII 字符的输出也会失败——并且 C++ 标准库实现不提供解决此问题的解决方法。
#include <iostream>
#include <string>
using namespace std;
auto main() -> int
{
wstring username;
wcout << L"Hi, what’s your name? ";
getline( wcin, username );
wcout << "Pleased to meet you, " << username << "!\n";
}
H:\personal\web\blog alf on programming at wordpress[=18=]2\code>chcp 65001
Active code page: 65001
H:\personal\web\blog alf on programming at wordpress[=18=]2\code>g++ problem.input.cpp -std=c++14
H:\personal\web\blog alf on programming at wordpress[=18=]2\code>a
Hi, whatSøren Moskégård
← No visible output.
H:\personal\web\blog alf on programming at wordpress[=18=]2\code>_
在 Windows API 级别,解决方案是使用非基于流的 直接控制台 i/o 当相关标准流绑定到控制台。例如,使用 WriteConsole
API 函数。并且作为Visual C++和MinGW g++标准库都支持的扩展,可以为使用WriteConsole
的标准宽流设置一个模式,还有一个转换to/fromUTF-8的模式作为外部编码。
在 Unix 领域,对 setlocale( LC_ALL, "" )
或其更高级别的 C++ 等效项的单个调用足以使宽流工作。
但是如何透明和自动地设置这些模式,以便使用宽流的相同普通标准 C++ 代码在 Windows 和 Unix-land 中都能工作?
请注意,对于在 Unix 程序中使用宽文本的想法感到不寒而栗的读者来说,这实际上是使用可移植代码的先决条件 Unix 领域的 UTF-8 窄文本控制台 i/o。也就是说,在 Unix 领域自动使用 UTF-8 窄文本和在 Windows 中使用宽文本的代码成为可能,并且可以构建在 Windows 对 Unicode 的支持之上。但是没有这样的支持,一般情况下没有可移植性。
问题 #2:
- 使用宽流时,输出项目到
wchar_t const*
的默认转换不起作用。
#include <iostream>
using namespace std;
struct Byte_string
{ operator char const* () const { return "Hurray, it works!"; } };
struct Wide_string
{ operator wchar_t const* () const { return L"Hurray, it works!"; } };
auto main() -> int
{
wcout << "Byte string pointer: " << Byte_string() << endl;
wcout << "Wide string pointer: " << Wide_string() << endl;
}
Byte string pointer: Hurray, it works!
Wide string pointer: 0x4ad018
这是标准中实现层面的不一致类型的缺陷,我很早以前就报告过了。我不确定状态,它可能已被遗忘(我从未收到任何关于它的邮件),或者可能会在 C++17 中应用修复程序。无论如何,如何解决这个问题?
简而言之,如何编写使用 Unicode 宽文本控制台 i/o 的标准 C++ 代码,在 Windows 和 Unix 领域都工作并实用?
修复转换问题:
cppx/stdlib/iostreams_conversion_defect.fix.hpp
#pragma once
//----------------------------------------------------------------------------------------
// PROBLEM DESCRIPTION.
//
// Output of wchar_t const* is only supported via an operator<< template. User-defined
// conversions are not considered for template matching. This results in actual argument
// with user conversion to wchar_t const*, for a wide stream, being presented as the
// pointer value instead of the string.
#include <iostream>
#ifndef CPPX_NO_IOSTREAM_CONVERSION_FIX
namespace std{
template< class Char_traits >
inline auto operator<<(
basic_ostream<wchar_t, Char_traits>& stream,
wchar_t const ch
)
-> basic_ostream<wchar_t, Char_traits>&
{ return operator<< <wchar_t, Char_traits>( stream, ch ); }
template< class Char_traits >
inline auto operator<<(
basic_ostream<wchar_t, Char_traits>& stream,
wchar_t const* const s
)
-> basic_ostream<wchar_t, Char_traits>&
{ return operator<< <wchar_t, Char_traits>( stream, s ); }
} // namespace std
#endif
在Windows中设置直接i/o模式:
这是 Visual C++ 和 MinGW g++ 都支持的标准库扩展。
首先,仅仅因为它在代码中使用,Ptr
类型构建器的定义(library-provided 类型构建器的主要缺点是普通类型推断不会启动,即它是必要 在某些情况下仍然使用原始运算符表示法):
cppx/core_language/type_builders.hpp
⋮
template< class T > using Ptr = T*;
⋮
一个辅助定义,因为它被用在多个文件中:
cppx/stdlib/Iostream_mode.hpp
#pragma once
// Mode for a possibly console-attached iostream, such as std::wcout.
namespace cppx {
enum Iostream_mode: int { unknown, utf_8, direct_io };
} // namespace cppx
模式设置器(基本功能):
cppx/stdlib/impl/utf8_mode.for_windows.hpp
#pragma once
// UTF-8 mode for a stream in Windows.
#ifndef _WIN32
# error This is a Windows only implementation.
#endif
#include <cppx/stdlib/Iostream_mode.hpp>
#include <stdio.h> // FILE, stdin, stdout, stderr, etc.
// Non-standard headers, which are de facto standard in Windows:
#include <io.h> // _setmode, _isatty, _fileno etc.
#include <fcntl.h> // _O_WTEXT etc.
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > f )
-> Iostream_mode
{
const int file_number = _fileno( f ); // See docs for error handling.
if( file_number == -1 ) { return Iostream_mode::unknown; }
const int new_mode = (_isatty( file_number )? _O_WTEXT : _O_U8TEXT);
const int previous_mode = _setmode( file_number, new_mode );
return (0?Iostream_mode()
: previous_mode == -1? Iostream_mode::unknown
: new_mode == _O_WTEXT? Iostream_mode::direct_io
: Iostream_mode::utf_8
);
}
} // namespace cppx
cppx/stdlib/impl/utf8_mode.generic.hpp
#pragma once
#include <stdio.h> // FILE, stdin, stdout, stderr, etc.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > )
-> Iostream_mode
{ return Iostream_mode::unknown; }
} // namespace cppx
cppx/stdlib/utf8_mode.hpp
#pragma once
// UTF-8 mode for a stream. For Unix-land this is a no-op & the locale must be UTF-8.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
#include <cppx/stdlib/Iostream_mode.hpp>
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > ) -> Iostream_mode;
} // namespace cppx
#ifdef _WIN32 // This also covers 64-bit Windows.
# include "impl/utf8_mode.for_windows.hpp" // Using Windows-specific _setmode.
#else
# include "impl/utf8_mode.generic.hpp" // A do-nothing implementation.
#endif
配置标准流。
除了在Windows中适当设置直接控制台i/o模式或UTF-8外,还修复了隐式转换缺陷; (间接)调用 setlocale
以便宽流在 Unix-land 中工作;设置 boolalpha
只是为了更好的衡量标准,作为更合理的默认值;并包括所有与 iostreams 相关的标准库 header(我没有显示单独的 header 文件,这在一定程度上是个人偏好,包括多少或是否做完全包含这样的内容):
cppx/stdlib/iostreams.hpp
#pragma once
// Standard iostreams but configured to work, plus, as utility, with boolalpha set.
#include <raw_stdlib/iostreams.hpp> // <iostream>, <sstream>, <fstream> etc. for convenience.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
#include <cppx/stdlib/utf8_mode.hpp> // stdin etc., stdlib::set_utf8_mode
#include <locale> // std::locale
#include <string> // std::string
#include <cppx/stdlib/impl/iostreams_conversion_defect.fix.hpp> // Support arg conv.
inline auto operator<< ( std::wostream& stream, const std::string& s )
-> std::wostream&
{ return (stream << s.c_str()); }
// The following code's sole purpose is to automatically initialize the streams.
namespace cppx { namespace utf8_iostreams {
using std::locale;
using std::ostream;
using std::cin; using std::cout; using std::cerr; using std::clog;
using std::wostream;
using std::wcin; using std::wcout; using std::wcerr; using std::wclog;
using std::boolalpha;
namespace detail {
using std::wstreambuf;
// Based on "Filtering streambufs" code by James Kanze published at
// <url: http://gabisoft.free.fr/articles/fltrsbf1.html>.
class Correcting_input_buffer
: public wstreambuf
{
private:
wstreambuf* provider_;
wchar_t buffer_;
protected:
auto underflow()
-> int_type override
{
if( gptr() < egptr() ) { return *gptr(); }
const int_type result = provider_->sbumpc();
if( result == L'\n' )
{
// Ad hoc workaround for g++ extra newline undesirable behavior:
provider_->pubsync();
}
if( traits_type::not_eof( result ) )
{
buffer_ = result;
setg( &buffer_, &buffer_, &buffer_ + 1 );
}
return result ;
}
public:
Correcting_input_buffer( wstreambuf* a_provider )
: provider_( a_provider )
{}
};
} // namespace detail
class Usage
{
private:
static
void init_once()
{
// In Windows there is no UTF-8 encoding spec for the locale, in Unix-land
// it's the default. From Microsoft's documentation: "If you provide a code
// page like UTF-7 or UTF-8, setlocale will fail, returning NULL". Still
// this call is essential for making the wide streams work correctly in
// Unix-land.
locale::global( locale( "" ) ); // Effects a `setlocale( LC_ALL, "" )`.
for( const Ptr<FILE> c_stream : {stdin, stdout, stderr} )
{
const auto new_mode = set_utf8_mode( c_stream );
if( c_stream == stdin && new_mode == Iostream_mode::direct_io )
{
static detail::Correcting_input_buffer correcting_buffer( wcin.rdbuf() );
wcin.rdbuf( &correcting_buffer );
}
}
for( const Ptr<ostream> stream_ptr : {&cout, &cerr, &clog} )
{
*stream_ptr << boolalpha;
}
for( const Ptr<wostream> stream_ptr : {&wcout, &wcerr, &wclog} )
{
*stream_ptr << boolalpha;
}
}
public:
Usage()
{ static const bool dummy = (init_once(), true); (void) dummy; }
};
namespace detail {
const Usage usage;
} // namespace detail
}} // namespace cppx::utf8_iostreams
问题中的两个示例程序只需包含上面的 header 而不是 <iostream>
或除此之外即可修复。除了它之外,它还可以位于单独的翻译单元中(隐式转换缺陷修复除外,如果需要,必须以某种方式包含它的 header )。或者例如作为构建命令中的强制包含。
注意:这是 question-with-answer 的目的是为了记录其他人可能会觉得有用的技术,并且可能会意识到其他人甚至更好的解决方案。请随时添加评论或问题作为评论。也请随时添加其他答案。 :)
问题 #1:
- 通过流 对 Unicode 的控制台支持在 Windows API 级别受到严重限制。可用于普通桌面应用程序的唯一相关代码页是 65001,UTF-8。然后交互式输入在 API 级别失败,甚至非 ASCII 字符的输出也会失败——并且 C++ 标准库实现不提供解决此问题的解决方法。
#include <iostream>
#include <string>
using namespace std;
auto main() -> int
{
wstring username;
wcout << L"Hi, what’s your name? ";
getline( wcin, username );
wcout << "Pleased to meet you, " << username << "!\n";
}
H:\personal\web\blog alf on programming at wordpress[=18=]2\code>chcp 65001 Active code page: 65001 H:\personal\web\blog alf on programming at wordpress[=18=]2\code>g++ problem.input.cpp -std=c++14 H:\personal\web\blog alf on programming at wordpress[=18=]2\code>a Hi, whatSøren Moskégård ← No visible output. H:\personal\web\blog alf on programming at wordpress[=18=]2\code>_
在 Windows API 级别,解决方案是使用非基于流的 直接控制台 i/o 当相关标准流绑定到控制台。例如,使用 WriteConsole
API 函数。并且作为Visual C++和MinGW g++标准库都支持的扩展,可以为使用WriteConsole
的标准宽流设置一个模式,还有一个转换to/fromUTF-8的模式作为外部编码。
在 Unix 领域,对 setlocale( LC_ALL, "" )
或其更高级别的 C++ 等效项的单个调用足以使宽流工作。
但是如何透明和自动地设置这些模式,以便使用宽流的相同普通标准 C++ 代码在 Windows 和 Unix-land 中都能工作?
请注意,对于在 Unix 程序中使用宽文本的想法感到不寒而栗的读者来说,这实际上是使用可移植代码的先决条件 Unix 领域的 UTF-8 窄文本控制台 i/o。也就是说,在 Unix 领域自动使用 UTF-8 窄文本和在 Windows 中使用宽文本的代码成为可能,并且可以构建在 Windows 对 Unicode 的支持之上。但是没有这样的支持,一般情况下没有可移植性。
问题 #2:
- 使用宽流时,输出项目到
wchar_t const*
的默认转换不起作用。
#include <iostream>
using namespace std;
struct Byte_string
{ operator char const* () const { return "Hurray, it works!"; } };
struct Wide_string
{ operator wchar_t const* () const { return L"Hurray, it works!"; } };
auto main() -> int
{
wcout << "Byte string pointer: " << Byte_string() << endl;
wcout << "Wide string pointer: " << Wide_string() << endl;
}
Byte string pointer: Hurray, it works! Wide string pointer: 0x4ad018
这是标准中实现层面的不一致类型的缺陷,我很早以前就报告过了。我不确定状态,它可能已被遗忘(我从未收到任何关于它的邮件),或者可能会在 C++17 中应用修复程序。无论如何,如何解决这个问题?
简而言之,如何编写使用 Unicode 宽文本控制台 i/o 的标准 C++ 代码,在 Windows 和 Unix 领域都工作并实用?
修复转换问题:
cppx/stdlib/iostreams_conversion_defect.fix.hpp#pragma once
//----------------------------------------------------------------------------------------
// PROBLEM DESCRIPTION.
//
// Output of wchar_t const* is only supported via an operator<< template. User-defined
// conversions are not considered for template matching. This results in actual argument
// with user conversion to wchar_t const*, for a wide stream, being presented as the
// pointer value instead of the string.
#include <iostream>
#ifndef CPPX_NO_IOSTREAM_CONVERSION_FIX
namespace std{
template< class Char_traits >
inline auto operator<<(
basic_ostream<wchar_t, Char_traits>& stream,
wchar_t const ch
)
-> basic_ostream<wchar_t, Char_traits>&
{ return operator<< <wchar_t, Char_traits>( stream, ch ); }
template< class Char_traits >
inline auto operator<<(
basic_ostream<wchar_t, Char_traits>& stream,
wchar_t const* const s
)
-> basic_ostream<wchar_t, Char_traits>&
{ return operator<< <wchar_t, Char_traits>( stream, s ); }
} // namespace std
#endif
在Windows中设置直接i/o模式:
这是 Visual C++ 和 MinGW g++ 都支持的标准库扩展。
首先,仅仅因为它在代码中使用,Ptr
类型构建器的定义(library-provided 类型构建器的主要缺点是普通类型推断不会启动,即它是必要 在某些情况下仍然使用原始运算符表示法):
⋮
template< class T > using Ptr = T*;
⋮
一个辅助定义,因为它被用在多个文件中:
cppx/stdlib/Iostream_mode.hpp#pragma once
// Mode for a possibly console-attached iostream, such as std::wcout.
namespace cppx {
enum Iostream_mode: int { unknown, utf_8, direct_io };
} // namespace cppx
模式设置器(基本功能):
cppx/stdlib/impl/utf8_mode.for_windows.hpp#pragma once
// UTF-8 mode for a stream in Windows.
#ifndef _WIN32
# error This is a Windows only implementation.
#endif
#include <cppx/stdlib/Iostream_mode.hpp>
#include <stdio.h> // FILE, stdin, stdout, stderr, etc.
// Non-standard headers, which are de facto standard in Windows:
#include <io.h> // _setmode, _isatty, _fileno etc.
#include <fcntl.h> // _O_WTEXT etc.
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > f )
-> Iostream_mode
{
const int file_number = _fileno( f ); // See docs for error handling.
if( file_number == -1 ) { return Iostream_mode::unknown; }
const int new_mode = (_isatty( file_number )? _O_WTEXT : _O_U8TEXT);
const int previous_mode = _setmode( file_number, new_mode );
return (0?Iostream_mode()
: previous_mode == -1? Iostream_mode::unknown
: new_mode == _O_WTEXT? Iostream_mode::direct_io
: Iostream_mode::utf_8
);
}
} // namespace cppx
cppx/stdlib/impl/utf8_mode.generic.hpp
#pragma once
#include <stdio.h> // FILE, stdin, stdout, stderr, etc.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > )
-> Iostream_mode
{ return Iostream_mode::unknown; }
} // namespace cppx
cppx/stdlib/utf8_mode.hpp
#pragma once
// UTF-8 mode for a stream. For Unix-land this is a no-op & the locale must be UTF-8.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
#include <cppx/stdlib/Iostream_mode.hpp>
namespace cppx {
inline
auto set_utf8_mode( const Ptr< FILE > ) -> Iostream_mode;
} // namespace cppx
#ifdef _WIN32 // This also covers 64-bit Windows.
# include "impl/utf8_mode.for_windows.hpp" // Using Windows-specific _setmode.
#else
# include "impl/utf8_mode.generic.hpp" // A do-nothing implementation.
#endif
配置标准流。
除了在Windows中适当设置直接控制台i/o模式或UTF-8外,还修复了隐式转换缺陷; (间接)调用 setlocale
以便宽流在 Unix-land 中工作;设置 boolalpha
只是为了更好的衡量标准,作为更合理的默认值;并包括所有与 iostreams 相关的标准库 header(我没有显示单独的 header 文件,这在一定程度上是个人偏好,包括多少或是否做完全包含这样的内容):
#pragma once
// Standard iostreams but configured to work, plus, as utility, with boolalpha set.
#include <raw_stdlib/iostreams.hpp> // <iostream>, <sstream>, <fstream> etc. for convenience.
#include <cppx/core_language/type_builders.hpp> // cppx::Ptr
#include <cppx/stdlib/utf8_mode.hpp> // stdin etc., stdlib::set_utf8_mode
#include <locale> // std::locale
#include <string> // std::string
#include <cppx/stdlib/impl/iostreams_conversion_defect.fix.hpp> // Support arg conv.
inline auto operator<< ( std::wostream& stream, const std::string& s )
-> std::wostream&
{ return (stream << s.c_str()); }
// The following code's sole purpose is to automatically initialize the streams.
namespace cppx { namespace utf8_iostreams {
using std::locale;
using std::ostream;
using std::cin; using std::cout; using std::cerr; using std::clog;
using std::wostream;
using std::wcin; using std::wcout; using std::wcerr; using std::wclog;
using std::boolalpha;
namespace detail {
using std::wstreambuf;
// Based on "Filtering streambufs" code by James Kanze published at
// <url: http://gabisoft.free.fr/articles/fltrsbf1.html>.
class Correcting_input_buffer
: public wstreambuf
{
private:
wstreambuf* provider_;
wchar_t buffer_;
protected:
auto underflow()
-> int_type override
{
if( gptr() < egptr() ) { return *gptr(); }
const int_type result = provider_->sbumpc();
if( result == L'\n' )
{
// Ad hoc workaround for g++ extra newline undesirable behavior:
provider_->pubsync();
}
if( traits_type::not_eof( result ) )
{
buffer_ = result;
setg( &buffer_, &buffer_, &buffer_ + 1 );
}
return result ;
}
public:
Correcting_input_buffer( wstreambuf* a_provider )
: provider_( a_provider )
{}
};
} // namespace detail
class Usage
{
private:
static
void init_once()
{
// In Windows there is no UTF-8 encoding spec for the locale, in Unix-land
// it's the default. From Microsoft's documentation: "If you provide a code
// page like UTF-7 or UTF-8, setlocale will fail, returning NULL". Still
// this call is essential for making the wide streams work correctly in
// Unix-land.
locale::global( locale( "" ) ); // Effects a `setlocale( LC_ALL, "" )`.
for( const Ptr<FILE> c_stream : {stdin, stdout, stderr} )
{
const auto new_mode = set_utf8_mode( c_stream );
if( c_stream == stdin && new_mode == Iostream_mode::direct_io )
{
static detail::Correcting_input_buffer correcting_buffer( wcin.rdbuf() );
wcin.rdbuf( &correcting_buffer );
}
}
for( const Ptr<ostream> stream_ptr : {&cout, &cerr, &clog} )
{
*stream_ptr << boolalpha;
}
for( const Ptr<wostream> stream_ptr : {&wcout, &wcerr, &wclog} )
{
*stream_ptr << boolalpha;
}
}
public:
Usage()
{ static const bool dummy = (init_once(), true); (void) dummy; }
};
namespace detail {
const Usage usage;
} // namespace detail
}} // namespace cppx::utf8_iostreams
问题中的两个示例程序只需包含上面的 header 而不是 <iostream>
或除此之外即可修复。除了它之外,它还可以位于单独的翻译单元中(隐式转换缺陷修复除外,如果需要,必须以某种方式包含它的 header )。或者例如作为构建命令中的强制包含。