这是 boost::filesystem 中的错误吗?为什么 boost::filesystem::path::string() 在 Windows 和 Linux 上没有相同的签名?
Is this a bug in boost::filesystem? Why does boost::filesystem::path::string() not have the same signature on Windows and Linux?
我正在尝试使用成员函数 string()
将 boost::filesystem::path
的向量转换为 std::string
。我写了这篇文章,它在 Windows (MSVC 14, 2015) 上运行良好:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
std::mem_fn(static_cast<const std::string (PathType::*)() const>(
&PathType::string)));
现在我转到 gcc(6.3,Debian Stretch),我的代码给出了链接错误,上面的签名不存在。要修复它,我必须将代码更改为:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
std::mem_fn(static_cast<const std::string& (PathType::*)() const>(
&PathType::string)))
PS:我知道 lambda 解决方案更简单,出于必要,我现在改用它了。
起初,我认为 MSVC 更宽容,但后来我切换回 Windows 并得到相反的链接错误,第一个签名是正确的。我去了源代码(1.64,path.hpp
),这是我发现的:
# ifdef BOOST_WINDOWS_API
const std::string string() const
{
std::string tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
//...
# else // BOOST_POSIX_API
// string_type is std::string, so there is no conversion
const std::string& string() const { return m_pathname; }
//...
# endif
所以我看到的推理是 Windows,因为它默认不使用 UTF-8,所以有一个临时转换。但为什么 boost 不对 Windows 和 Linux 使用相同的 API?最坏的情况是,它会花费一个字符串的副本。对吧?
我应该使用 path::string()
的替代方法来获得跨平台 API 稳定性吗?
您可能使用了旧版本的 Boost.Filesystem 库。 Boost 1.64 says签名是:
string string(const codecvt_type& cvt=codecvt()) const;
return 类型不依赖于平台;它应该始终是一个值,而不是一个引用。请注意,这(大部分)匹配 the C++17 FileSystem library's definition。因此,如果您在文档中说它是一个值时获得参考,那么其中一个是错误的。因此,无论哪种方式都存在错误。
但是,应该注意的是,在 C++ 标准中(因此,也可能在 Boost 中),成员函数的假设是它们不必 完全 符合文档规范。例如,成员函数可以具有标准中未列出的其他默认参数。只要它按照说明是可调用的,那就是一个有效的实现。
因此,您根本不应该期望 std::mem_fn
像这样工作 。使用 C++ 标准措辞,不应假设 path::string
可以转换为具有该签名的成员指针。因此,虽然它可能不一致,但您可以获得成员指针的期望可能不是 Boost 支持的接口。
无论是否是错误,您都可以使用 lambda 轻松解决此问题:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
[](const auto &pth) -> decltype(auto) {return pth.string();});
它比 std::mem_fn
版本看起来干净多了。如果 return 是引用,decltype(auto)
会阻止不必要的复制。
如评论中所述,Windows 路径存储为 2 字节 UTF-16 宽字符,因此需要转换为 std::string
。 Boost's path.hpp 对 Windows 进行了以下转换 API wstring
未在此处进行转换。
# ifdef BOOST_WINDOWS_API
const std::string string() const
{
std::string tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
// string_type is std::wstring, so there is no conversion
const std::wstring& wstring() const { return m_pathname; }
但是LinuxAPI的转换后,wstring
在这里转换
# else // BOOST_POSIX_API
// string_type is std::string, so there is no conversion
const std::string& string() const { return m_pathname; }
const std::wstring wstring() const
{
std::wstring tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
如需进一步阅读,您还可以参考 this answer。
我正在尝试使用成员函数 string()
将 boost::filesystem::path
的向量转换为 std::string
。我写了这篇文章,它在 Windows (MSVC 14, 2015) 上运行良好:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
std::mem_fn(static_cast<const std::string (PathType::*)() const>(
&PathType::string)));
现在我转到 gcc(6.3,Debian Stretch),我的代码给出了链接错误,上面的签名不存在。要修复它,我必须将代码更改为:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
std::mem_fn(static_cast<const std::string& (PathType::*)() const>(
&PathType::string)))
PS:我知道 lambda 解决方案更简单,出于必要,我现在改用它了。
起初,我认为 MSVC 更宽容,但后来我切换回 Windows 并得到相反的链接错误,第一个签名是正确的。我去了源代码(1.64,path.hpp
),这是我发现的:
# ifdef BOOST_WINDOWS_API
const std::string string() const
{
std::string tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
//...
# else // BOOST_POSIX_API
// string_type is std::string, so there is no conversion
const std::string& string() const { return m_pathname; }
//...
# endif
所以我看到的推理是 Windows,因为它默认不使用 UTF-8,所以有一个临时转换。但为什么 boost 不对 Windows 和 Linux 使用相同的 API?最坏的情况是,它会花费一个字符串的副本。对吧?
我应该使用 path::string()
的替代方法来获得跨平台 API 稳定性吗?
您可能使用了旧版本的 Boost.Filesystem 库。 Boost 1.64 says签名是:
string string(const codecvt_type& cvt=codecvt()) const;
return 类型不依赖于平台;它应该始终是一个值,而不是一个引用。请注意,这(大部分)匹配 the C++17 FileSystem library's definition。因此,如果您在文档中说它是一个值时获得参考,那么其中一个是错误的。因此,无论哪种方式都存在错误。
但是,应该注意的是,在 C++ 标准中(因此,也可能在 Boost 中),成员函数的假设是它们不必 完全 符合文档规范。例如,成员函数可以具有标准中未列出的其他默认参数。只要它按照说明是可调用的,那就是一个有效的实现。
因此,您根本不应该期望 std::mem_fn
像这样工作 。使用 C++ 标准措辞,不应假设 path::string
可以转换为具有该签名的成员指针。因此,虽然它可能不一致,但您可以获得成员指针的期望可能不是 Boost 支持的接口。
无论是否是错误,您都可以使用 lambda 轻松解决此问题:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
[](const auto &pth) -> decltype(auto) {return pth.string();});
它比 std::mem_fn
版本看起来干净多了。如果 return 是引用,decltype(auto)
会阻止不必要的复制。
如评论中所述,Windows 路径存储为 2 字节 UTF-16 宽字符,因此需要转换为 std::string
。 Boost's path.hpp 对 Windows 进行了以下转换 API wstring
未在此处进行转换。
# ifdef BOOST_WINDOWS_API
const std::string string() const
{
std::string tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
// string_type is std::wstring, so there is no conversion
const std::wstring& wstring() const { return m_pathname; }
但是LinuxAPI的转换后,wstring
在这里转换
# else // BOOST_POSIX_API
// string_type is std::string, so there is no conversion
const std::string& string() const { return m_pathname; }
const std::wstring wstring() const
{
std::wstring tmp;
if (!m_pathname.empty())
path_traits::convert(&*m_pathname.begin(), &*m_pathname.begin()+m_pathname.size(),
tmp);
return tmp;
}
如需进一步阅读,您还可以参考 this answer。