C++11 std::cout << "string literal in UTF-8" 到 Windows cmd 控制台? (Visual Studio 2015)

C++11 std::cout << "string literal in UTF-8" to Windows cmd console? (Visual Studio 2015)

总结:我应该怎么做才能正确打印源代码中定义的字符串文字,该字符串文字以 UTF-8 编码(Windows CP 65001)存储到使用 std::cout 流的 cmd 控制台?

动机: 想把优秀的Catch unit-testing framework (as an experiment) so that it would display my texts改成重音字。修改应该简单、可靠,并且对其他语言和工作环境也有用,这样它才能被作者接受为增强。或者,如果您知道 Catch 并且有其他替代解决方案,您可以 post 吗?

详情:让我们从"quick brown fox..."

的捷克语版本开始
#include <iostream>
#include "windows.h"

using namespace std;

int main()
{
    cout << "\n-------------------------- default cmd encoding = 852 -------------------\n";
    cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << endl;

    cout << "\n-------- Windows Central European (1250) set for the cmd console --------\n";
    SetConsoleOutputCP(1250);
    std::cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << std::endl;

    cout << "\n------------- Windows UTF-8 (65001) set for the cmd console -------------\n";
    SetConsoleOutputCP(CP_UTF8);
    std::cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << std::endl;
}

它打印以下内容(字体设置为 Lucida Console):

cmd默认编码为852,默认windows编码为1250,源码使用65001编码保存(UTF-8 with BOM)。 SetConsoleOutputCP(1250); 以与 chcp 1250 相同的方式更改 cmd 编码(以编程方式)。

观察:设置 1250 编码时,UTF-8 字符串字面值打印正确。我相信它可以解释,但它确实很奇怪。有没有什么体面的,human,解决问题的通用方法?

更新: "narrow string literal" 在我的例子中使用 Windows-1250 编码存储(中欧的原生 Windows 编码)。它似乎与源代码的编码无关。编译器将其保存在 windows 原生编码 中。因此,将 cmd 切换到该编码会提供所需的输出。这很丑陋,但我怎样才能以编程方式获得 本机 windows 编码 (将其传递给 SetConsoleOutputCP(cpX))?我需要的是一个对编译发生的机器有效的常量。它不应该是运行可执行文件的机器的本机编码。

C++11也引入了u8"the UTF-8 string literal",但似乎不适合SetConsoleOutputCP(CP_UTF8);

这是通过 luk32 跳转 link 并确认 Melebius 评论(见下面的问题)找到的部分答案。这不是完整的答案,我很乐意接受您的后续评论。

我刚刚发现 UTF-8 Everywhere Manifesto that touches the problem. The point 17. Q: How do I write UTF-8 string literal in my C++ code? 说(对于 Microsoft C++ 编译器也是明确的):

However the most straightforward way is to just write the string as-is and save the source file encoded in UTF-8:

                                "∃y ∀x ¬(x ≺ y)"

Unfortunately, MSVC converts it to some ANSI codepage, corrupting the string. To work around this, save the file in UTF-8 without BOM. MSVC will assume that it is in the correct codepage and will not touch your strings. However, it renders it impossible to use Unicode identifiers and wide string literals (that you will not be using anyway).

我真的很喜欢这个宣言。简而言之,使用粗鲁的词语,并且可能过于简单化,它说:

Ignore the wstring, wchar_t, and the like things. Ignore the codepages. Ignore the string literal prefixes like L, u, U, u8. Use UTF-8 everywhere. Write all literals "naturally". Ensure it is also stored in the compiled binary.

如果下面的代码是用UTF-8无BOM存储的...

#include <iomanip>
#include <iostream>
#include "windows.h"

using namespace std;

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    cout << "Příšerně žluťoučký kůň úpěl ďábelské ódy!" << endl;

    int cnt = 0;
    for (unsigned int c : "Příšerně žluťoučký kůň úpěl ďábelské ódy!") 
    {
        cout << hex << setw(2) << setfill('0') << (c & 0xff);
        ++cnt;
        if (cnt % 16 == 0)      cout << endl;
        else if (cnt % 8 == 0)  cout << " | ";
        else if (cnt % 4 == 0)  cout << "  ";
        else                    cout << ' ';
    }
    cout << endl;
}

它打印(应该是 UTF-8 编码的)...

将源文件保存为带有 BOM 的 UTF-8 时,它会打印不同的结果...

但是,问题仍然存在 -- 如何以编程方式设置控制台编码,以便正确打印 UTF-8 字符串。

我放弃了。 cmd 控制台简直是瘫痪了,不值得从外面修理它。我接受我自己的评论只是为了结束这个问题。如果有人找到与 Catch 单元测试框架相关的体面解决方案(可能完全不同),我将很高兴接受 his/her 评论作为答案。

MSVC 编译器尝试使用您的本地编码对代码中的常量字符串进行编码。在您的例子中,它使用 code page 852。因此,即使您的 cmd 输出尝试使用 code page 1250 读取和输出字符串,该字符串实际上存储为 code page 852。存储和读取之间的这种不兼容会导致错误的输出。
解决这个问题的一种方法是将字符串存储在用 code page 1250 编码的文件中。 Visual Studio Code 提供了这样的功能。您可以将文件作为二进制文件(即逐字节)读取到 char 缓冲区,然后输出缓冲区。

char * memblock = new char[1024];
std::ifstream file("src.txt", std::ios::in | std::ios::binary | std::ios::ate);
int size;
if (file.is_open())
{
    size = file.tellg();
    memblock = new char[size];
    file.seekg(0, std::ios::beg);
    file.read(memblock, size);
    file.close();
}
else
{
    std::cout << "File not opened." << std::endl;
}
memblock[size] = 0;
std::cout << memblock << std::endl;