C++ setw()在包含UTF-8多字节字符/代码点的字符串上嵌入错误的输出

C++ setw()在包含UTF-8多字节字符/代码点的字符串上嵌入错误的输出,c++,utf-8,locale,cout,setw,C++,Utf 8,Locale,Cout,Setw,我需要输出一些可能是UTF-8多字节的数据,并且需要使用setw()对它们进行格式化 当字符为多字节序列时,验证将丢失并且setw()无法正常工作 //#include <stdio.h> #include <locale> #include <iostream> //#include <fstream> #include <iomanip> //#include <sstream> int main(int argc,

我需要输出一些可能是UTF-8多字节的数据,并且需要使用
setw()
对它们进行格式化

当字符为多字节序列时,验证将丢失并且
setw()
无法正常工作

//#include <stdio.h>
#include <locale>
#include <iostream>
//#include <fstream>
#include <iomanip>
//#include <sstream>

int main(int argc, char **argv)
{ 
    std::locale l=std::locale("en_US.utf8");
    std::locale::global(l); 
    std::cout.imbue(l);
    std::cout<<std::endl;
    std::cout<<std::setw(40)<<std::right<<"hi “my” friend"<<std::endl;
    std::cout<<std::setw(40)<<std::right<<"hi -my- friend"<<std::endl;
    return 0;
}
我错过了什么

我必须指出,字符
不是普通的
,而是另外两个字符,它们在UTF-8中分别用三个字节表示。

字符串文字
“hi-my-friend”
包含14个字符。字符串文字
“hi”my“friend”“
包含18个字符:
cout
按原样输出这些字符,目标终端将3字节序列转换为单个符号

因此,从流的角度来看,一切都正常:它输出
(宽度-strlen(literal))
填充字符,然后
strlen(literal)
字符,
宽度
总计。它不处理可能的多字节序列,也不知道目标终端将几个字符转换为一个符号

String literal
“hi-my-friend”
包含14个字符。字符串文字
“嗨”我的“朋友”
包含18个字符:
cout
按原样输出这些字符,目标终端将3字节序列转换为单个符号


因此,从流的角度来看,一切都正常:它输出
(宽度-strlen(literal))
填充字符,然后
strlen(literal)
字符,
宽度
总计。它不处理可能的多字节序列,也不知道目标终端将几个字符转换为一个符号

您可以通过计算字符串在宽表示形式下的字符数来完成此格式化,然后计算字符串长度与宽表示形式之间的差异,然后将此差异添加到传递给
setw
的内容中,例如:

std::mbstate_t state = std::mbstate_t();
std::string s = "hi “my” friend";
const char *cp = s.c_str();
size_t len = mbsrtowcs(nullptr, &cp, s.size(), &state);
std::cout << setw(40 + (s.size() - len)) << std::right << s << std::endl;
std::mbstate\u t state=std::mbstate\u t();
std::string s=“嗨”我的“朋友”;
const char*cp=s.c_str();
size_t len=mbsrtowcs(nullptr,&cp,s.size(),&state);

std::cout您可以通过计算字符串在宽表示形式下的字符数来完成此格式化,然后计算字符串长度与宽表示形式之间的差异,然后将该差异添加到传递给
setw
的内容中,例如:

std::mbstate_t state = std::mbstate_t();
std::string s = "hi “my” friend";
const char *cp = s.c_str();
size_t len = mbsrtowcs(nullptr, &cp, s.size(), &state);
std::cout << setw(40 + (s.size() - len)) << std::right << s << std::endl;
std::mbstate\u t state=std::mbstate\u t();
std::string s=“嗨”我的“朋友”;
const char*cp=s.c_str();
size_t len=mbsrtowcs(nullptr,&cp,s.size(),&state);

std::cout可以预期具有区域设置知识的流将处理必要的转换。如果不是,则“setw”无效,因为它不执行用户期望的操作。“灌输”是什么意思?显然,不仅需要终端,还需要文件,因为这些文件可能包含utf8文本(或选择的任何编码)。@GeorgeKourtis如果你查看
locale
类,你会发现它实际上没有处理多字节编码的功能。整个本地化库和所有标准流都需要固定宽度的编码。它提供的唯一功能是
wstring\u convert
codevt.*
在编码之间进行转换的类。在将数据传递到标准库设施之前,您需要将数据转换为固定宽度编码。简而言之:您正在向它提供它无法处理的数据。要么将数据转换为固定宽度,要么不依赖于除原始字符输出以外的任何额外内容。具有区域设置知识的流将处理必要的转换。如果不是,则“setw”无效,因为它不执行用户期望的操作。“灌输”是什么意思?显然,不仅需要终端,还需要文件,因为这些文件可能包含utf8文本(或选择的任何编码)。@GeorgeKourtis如果你查看
locale
类,你会发现它实际上不需要处理多字节编码。整个本地化库和所有标准流都需要固定宽度的编码。它提供的唯一功能是
wstring\u convert
codevt.*
在编码之间进行转换的类。在将数据传递到标准库设施之前,您需要将数据转换为固定宽度编码。简而言之:您正在向它提供它无法处理的数据。要么将数据转换为固定宽度,要么不依赖除原始字符输出以外的任何额外内容。遗憾的是,嵌入UTF-8语言环境不会使格式化函数了解UTF-8。完成任务的最简单方法是将所有内容转换为wchar\t并使用宽字符流。遗憾的是,嵌入UTF-8语言环境不会使格式化函数了解UTF-8。完成任务的最简单方法是将所有内容转换为wchar\u t并使用宽字符流。“宽”字符不需要是UTF-16、UTF-32或任何Unicode编码。因此,无法保证该代码将产生预期的结果。即使是这样,如果“宽”字符是UTF-16,它也只能为适合单个UTF-16代码单元的代码点生成有用的结果。这将生成19个,其中仅需要4个字符来纠正对齐。这似乎是由
len
作为最大ulong64_t值触发的,该值在或溢出下触发,以及所有有趣的东西。看起来必须显式定义区域设置才能工作“宽”字符不需要是UTF-16、UTF-32或任何Unicode编码。因此,无法保证该代码将产生预期的结果。即使是这样,如果“宽”字符是UTF-16,它也只能为适合单个UTF-16代码单元的代码点生成有用的结果。这将生成19个,其中仅需要4个字符来纠正对齐。这似乎是由
size_t f(const std::string &s)
{
  std::mbstate_t state = std::mbstate_t();
  const char *cp = s.c_str();
  size_t len = mbsrtowcs(nullptr, &cp, s.size(), &state);
  return s.size() - len;
}
...

std::string s = "hi “my” friend";
std::cout << std::setw(40 + f(s)) << std::right << s << std::endl;