C++20弃用std::filesystem::u8path
:
在gcc.godbolt.org上运行
#include <filesystem>
std::string foo();
int main()
{
auto path = std::filesystem::u8path(foo());
}
libstdc++ 13有一个弃用警告:
<source>:7:40: warning: 'std::filesystem::__cxx11::path std::filesystem::__cxx11::u8path(
const _Source&) [with _Source = std::__cxx11::basic_string<char>; _Require = path; _CharT
= char]' is deprecated: use 'path((const char8_t*)&*source)' instead [-Wdeprecated-decla
rations]
7 | auto path = std::filesystem::u8path(foo());
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
对我来说,建议的强制转换path((const char8_t*)&*source)
看起来像是完全严格的别名冲突,因此是UB。
是这样吗?海湾合作委员会是否作出任何额外的保证,使这一法律的?
最后,如果我的路径存储在std::string
中,并且我不想将所有内容重写为std::u8string
,是否有更好的解决方法?
2条答案
按热度按时间noj0wjuj1#
简而言之,在你的例子中有未定义的行为。然而,实际的原因不是严格的别名冲突,而是由于假设的严格别名冲突而导致的先决条件冲突。
由于严格的别名,没有未定义的行为
不存在严格的别名冲突([basic.lval] p11),因为对字符的任何访问都将发生在
std::filesystem::path
的构造函数或文件系统库的其他部分中,并且这些可以被允许以用户不能的方式进行类型双关。(const char8_t*)&*
本质上是数据的reinterpret_cast<const char8_t*>
。reinterpret_cast
本身是有效的,即使通过指针访问对象是无效的。使用结果指针,您将调用以下构造函数:s
为源的effective range或范围[first, last)
,如果需要,可使用encoding converted。查找s
的检测格式,并构造一个类路径的对象,其格式中的路径名为s
。std::path
构造函数3path
的格式检测、参数格式转换以及类型和编码转换都是通过数学或散文定义的。例如,编码转换在[fs.path.type.cvt] p3中定义:对于采用表示路径的字符序列的成员函数参数和返回字符串的成员函数,如果参数或返回值的值类型不同于
path::value_type
,则执行值类型和编码转换。对于参数或返回值,转换方法和要转换的编码由其值类型确定:char8_t
:编码为UTF-8。转换方式不详。在实现这一点时,实现有很大的自由。例如,
std::filesystem::path
构造函数可以放宽别名规则。违反前置条件导致的未定义行为
问题在于值类型的使用:
一个输入迭代器
i
支持表达式*i
,导致一个对象类型T
的值,称为迭代器的 * 值类型 *。你的迭代器的类型是
const char8_t*
,间接(*i
)对它无效,因为它假设违反了严格别名。因此,您传递给path
构造函数的内容没有值类型,并且由于违反前提条件,因此行为未定义。GCC严格放宽字符类型之间的别名
我无法在GCC文档中找到有关此的详细信息,但
char8_t
似乎能够别名char
:因此,您可能依赖于编译器扩展。
ohtdti5x2#
免责声明:我是P0482 (char8_t: A type for UTF-8 characters and strings)提案的作者,该提案在C20中弃用
std::filesystem::u8path()
。libstdc警告提供的建议是正确的。通常这种强制转换是有问题的,但是
char
、unsigned char
和std::byte
被授予了特殊的权力来别名其他类型。这是由[basic.lval]p11提供的,它指出:如果一个程序试图通过一个glvalue来访问一个对象的存储值,而这个glvalue的类型不类似于以下类型之一,那么这个行为是未定义的:
*
char
、unsigned char
或std::byte
类型。...
但是,应尽可能避免别名转换,我推荐一种不同的解决方案。
std::filesystem::path
有一个构造函数模板,它接受一个经典的范围(一个迭代器对),并根据范围的值类型推导编码。对于保存在基于char
的存储中的UTF-8输入,只需要一个范围适配器将基于char
的值转换为char8_t
。这可以在没有运行时开销的情况下执行:这样的视图适配器非常有用,因此
charN_t
视图适配器家族正在考虑在P2728 (Unicode in the Library, Part 1: UTF Transcoding)(撰写本文时为修订版6)中进行标准化。如果需要,可以使用这样的视图适配器来提供
u8path()
替换,同样,不会造成运行时开销。如果你已经读到这一点,你现在可能想知道为什么
u8path
被弃用,如果它有可论证的用例。不幸的是,最初的提议缺乏对反对的辩护。其动机是,在弃用之前,标准库中只有两个接口需要保存UTF-8数据的基于char
的输入;所有其他接口都期望基于char
的输入保存以用于字符和字符串文字的普通文字编码或执行编码编码的文本数据。这两个接口是:std::filesystem::u8path()
std::codecvt_utf8
(已被P0618 (Deprecating )弃用)普通的文字编码和执行编码在实践中对于一些流行的(和一些不那么流行的)实现不是UTF-8,并且在可预见的未来预计仍然是这样。在没有类型系统支持的情况下,正确使用和维护以多种编码存储的文本是具有挑战性的,错误会导致质量和安全问题。重要的是,该标准不鼓励进一步使用
char
与UTF-8编码的文本;至少,不是可移植的代码,而且人们担心将来可能不得不为许多潜在的接口添加u8
前缀版本。无法使用类型系统根据编码进行重载,这将成为泛型代码库开发的一个障碍。注意:为了向后兼容现有的代码,将
u8""
字符串传递给std::filesystem::u8path()
,虽然不推荐,但std::filesystem::u8path()
通过P1423 (char8_t backward compatibility remediation)进行了修改,以接受char8_t
的范围。