如何在C++20中将表情符号存储在char8_t中并打印输出?

3
我刚刚听说了char8_tchar16_tchar32_t的存在,并正在测试它。当我尝试编译下面的代码时,g++会抛出以下错误:
error: use of deleted function ‘std::basic_ostream<char, _Traits>& std::operator<<(basic_ostream<char, _Traits>&, char32_t) [with _Traits = char_traits<char>]’
    6 |         std::cout << U'' << std::endl;
      |                      ^~~~~

#include <iostream>

int main() {
  char32_t c = U'';

  std::cout << c << std::endl;

  return 0;
}

此外,为什么我不能将表情符号放入char8_tchar16_t中呢?例如,下面的代码行不起作用:

char16_t c1 = u'';
char8_t c2 = u8'';
auto c3 = u'';
auto c4 = u8'';

据我了解,表情符号是UTF-8字符,因此应该适合于char8_t

3
字符编码为 utf-8 的字符可能超过 1 个字节。对于表情符号来说,这绝对是正确的情况。 - Kevin
这只是一个编码问题。你使用的是哪个编译器和平台? - Marek R
使用 char const* c = ""; - chrysante
这里有一个类似的问题,我在其中解释了如何在MSVC上处理:https://stackoverflow.com/a/67819605/1387438 请注意,如果您使用的是Windows和MinGW,则对区域设置的支持较差。在其他平台上,隐式使用UTF-8应该可以很容易地使其工作。 - Marek R
一个 uint8_t 中没有足够的空间来容纳所有 ASCII 字符和所有表情符号。你需要一个具有更多空间的数据结构。 - Thomas Matthews
4个回答

5

表情符号是UTF-8字符

“UTF-8字符”这种说法是不正确的。

实际上,有一种叫做Unicode编码点的概念。这些编码点可以用UTF-8编码表示,每个编码点都映射到一个或多个UTF-8代码单元即char8_t。这意味着大多数编码点都映射到多个char8_t,也就是一个字符串。而且表情符号不属于127个可映射为单个UTF-8代码单元的编码点之一。

特别地,表情符号可以由多个编码点构成,因此即使使用UTF-32编码,也不能保证任何表情符号都可以存储在单个char32_t编码点中。

最好始终将这些内容视为字符串,而不是字符。甚至可以忘记“字符”的存在。


3

代码

#include <iostream>

#ifdef _WIN32 
#include <Windows.h>
#define SET_CONSOLE_UTF8 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); //Set console output to UTF-8.Visual C++ code on Windows.
#endif // _WIN32 


#if defined(__cpp_char8_t) | defined(__cpp_lib_char8_t)

//Operator <<
std::ostream& operator<<(std::ostream& os, const std::u8string& str)
{
    os << reinterpret_cast<const char*>(str.data());
    return os;
}

//Convert u8string to string.
std::string ToString(const std::u8string& s) {
    return std::string(s.begin(), s.end());
}

std::u8string Tou8String(const std::string& s) {
    return std::u8string(s.begin(), s.end());
}

//const char8_t* literal to string. Operator ""_s
static inline std::string operator"" _s(const char8_t* value, size_t size) {
    static std::string x(reinterpret_cast<const char*>(value), size);
    return x;
}

#endif


using namespace std::string_literals;// operator ""s

int main() {
#ifdef _WIN32
    SET_CONSOLE_UTF8
#endif

    std::u8string u8String = u8""s;// u8string literal.
    std::string str = u8""_s; //Operator "_s". Convert utf8 literal(const char8_t*) to std::string. 

    std::cout << "string              " << str << std::endl; //Using operator << for std::string
    std::cout << "u8string -> string  " << ToString(u8String) << std::endl; //Using function ToString(u8string) -> string
    std::cout << "u8string            " << u8String << std::endl; //Using operator << for std::u8string.
    std::cout << "string -> u8string  " << Tou8String(str) << std::endl; //Using function Tou8String(string) -> u8string

    std::cin.get();
    return 0;
}

输出 Windows 终端和 https://godbolt.org/(Clang 和 GCC)

string              
u8string -> string  
u8string            
string -> u8string  

VisualC++

godbolt - CLANG

GCC


1
为什么不在提问时上传代码错误的截图?分享 godbolt 链接非常有效。这样任何人都可以进行操作。请注意,godbolt 隐式使用 UTF-8 编码。这在真实系统中可能不是默认设置。 - Marek R
我刚刚更新了答案。感谢您的推荐。 - Joma

2
当我尝试编译下面的代码时,g++会抛出以下错误:
窄流和宽流所期望的编码是与实现相关的,也可能取决于最终打印输出的终端所期望的编码。如果您想要打印到std::cout或std::wcout,则需要将字符转换为正确的编码,作为char或wchar_t类型。
此外,为什么我不能将表情符放入char8_t或char16_t中?例如,以下代码行不起作用:
该表情符是Unicode代码点U+1F60B,在UTF-8和UTF-16编码中都需要多个代码单元。但是,您正在尝试形成一个“字符字面量”,它只包含一个代码单元。
据我所知,表情符是UTF-8字符[...]
这不合理。UTF-8是Unicode代码点的编码方式。说一个字符“是UTF-8”是没有意义的。这表明您可能对Unicode和字符/字符串编码的工作原理有基本的误解。我建议您阅读一些关于这个主题的介绍。

2

这个有效

#include <iostream>

int main() {
  const char* c = "";

  std::cout << c << std::endl;

  return 0;
}

说明。

  1. 是一个多字节序列,不能适应单个char。因此应使用const char*
  2. 默认的源文件编码是UTF-8,因此Unicode字符只能在UTF-8编码中使用。对于char32_t,应写为U'\x1F60B'
  3. operator<<(std::basic_ostream)被删除了char8_tchar16_tchar32_t

在Windows上,它不能直接使用,因为系统通常使用特定于国家的单字节编码,其中没有完全支持Unicode。 - Marek R
默认的源文件编码是UTF-8,适用于哪个编译器? - Nicol Bolas
@NicolBolas 在 Linux 上,gcc 和 clang 隐式地使用 UTF-8。 - Marek R
@MarekR 刚在 VS 中尝试了一下,它提供了将文件保存为带有 BOM 的 UTF-8 的选项。因此,这不是一个问题。 - 273K
auto也支持这个 ;) https://godbolt.org/z/97b1exsc7 - Eriss69
@273K 这取决于您的机器设置。如果您配置了机器使用代码页 65001(UTF-8),那么这将直接起作用。我目前正在使用 codpeage 437(或 1252),控制台上的输出结果是 ≡ƒÿï。我必须添加 std::locale::global(std::locale(".utf-8")); std::cout.imbue(std::locale("")); 来使其正常工作,并将控制台代码页更改为支持此字符的内容(chcp 65001)。 - Marek R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接