Reputation: 26256
I'm trying to convert a vector of boost::filesystem::path
to std::string
, using the member function string()
. I wrote this and it was working fine on 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)));
Now I moved to gcc (6.3, Debian Stretch), and my code gave linking error that the signature above doesn't exist. To fix it, I had to change the code to:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
std::mem_fn(static_cast<const std::string& (PathType::*)() const>(
&PathType::string)))
PS: I know a lambda solution is easier, which I now switched to, out of necessity.
At first, I thought MSVC is more tolerant, but then I switched back to Windows and got the opposite linking error, that the first signature is correct. I went to the source code (1.64, path.hpp
), and this is what I found:
# 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
So the reasoning I see is that on Windows, since it doesn't use UTF-8 by default, there's a temporary conversion. But why wouldn't boost use the same API for both Windows and Linux? Worst case, it'll cost a copy of a string. Right?
Is there an alternative to path::string()
that I should be using to have cross-platform API stability?
Upvotes: 4
Views: 1650
Reputation: 473232
You may be using an old version of the Boost.Filesystem library. Boost 1.64 says the signature is:
string string(const codecvt_type& cvt=codecvt()) const;
The return type is not platform-dependent; it should always be a value, not a reference. Note that this (mostly) matches the C++17 FileSystem library's definition. So if you're getting a reference when the documentation says it's a value, then one of them is wrong. And thus, there's a bug either way.
However, it should be noted that in the C++ standard (and therefore, likely in Boost as well), the assumption for member functions is that they do not have to exactly match the documented specification. For example, a member function can have additional default parameters not listed in the standard. So long as it is callable as stated, that is a valid implementation.
Therefore, you should not expect std::mem_fn
to work like this at all. Using C++ standard wording, there should be no assumption that path::string
can be converted to a member pointer with that signature. So while it may be inconsistent, the expectation that you can get a member pointer may not be a supported interface for Boost.
Whether it's a bug or not, you can resolve this easily enough by using a lambda:
std::transform(
users.begin(), users.end(), std::back_inserter(usersStrs),
[](const auto &pth) -> decltype(auto) {return pth.string();});
It's a lot cleaner looking than the std::mem_fn
version. The decltype(auto)
prevents an unnecessary copy if it returns a reference.
Upvotes: 5
Reputation: 1046
As mentioned in the comments that Windows path is stored as 2-byte UTF-16 wide chars so conversion to std::string
is required. Boost's path.hpp has following conversion for Windows API wstring
is not converted here.
# 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; }
But following conversion for Linux API, wstring
is converted here
# 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;
}
for further reading you can also consult this answer.
Upvotes: 1