The Quantum Physicist
The Quantum Physicist

Reputation: 26256

Is this a bug in boost::filesystem? Why does boost::filesystem::path::string() not have the same signature on Windows and Linux?

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

Answers (2)

Nicol Bolas
Nicol Bolas

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

Sahib Yar
Sahib Yar

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

Related Questions