Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

g++ breaking change in std::filesystem::last_write_time

Tags:

c++

g++

c++17

With recent OS upgrade I noticed that small part of my code stopped compiling - it seems the reason is switching from g++-8 to g++-9.

This code is compiling alright on g++ 8.3.0 (verified this using gcc:8.3 image from Dockerhub)

#include <filesystem>
#include <chrono>

int main() {
    namespace fs = std::filesystem;
    using namespace std::chrono_literals;
    std::filesystem::last_write_time("test", std::chrono::system_clock::now() - 5min);
}

On g++ 9.1.0 it fails with:

test.cpp: In function 'int main()':
test.cpp:8:69: error: no matching function for call to 'last_write_time(const char [5], std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >)'
    8 |  fs::last_write_time("test", std::chrono::system_clock::now() - 5min);
      |                                                                     ^
In file included from /usr/local/include/c++/9.1.0/filesystem:39,
                 from test.cpp:1:
/usr/local/include/c++/9.1.0/bits/fs_ops.h:243:19: note: candidate: 'std::filesystem::file_time_type std::filesystem::last_write_time(const std::filesystem::__cxx11::path&)'
  243 |   file_time_type  last_write_time(const path& __p);
      |                   ^~~~~~~~~~~~~~~
/usr/local/include/c++/9.1.0/bits/fs_ops.h:243:19: note:   candidate expects 1 argument, 2 provided
/usr/local/include/c++/9.1.0/bits/fs_ops.h:244:19: note: candidate: 'std::filesystem::file_time_type std::filesystem::last_write_time(const std::filesystem::__cxx11::path&, std::error_code&)'
  244 |   file_time_type  last_write_time(const path& __p, error_code& __ec) noexcept;
      |                   ^~~~~~~~~~~~~~~
In file included from /usr/local/include/c++/9.1.0/filesystem:36,
                 from test.cpp:1:
/usr/local/include/c++/9.1.0/bits/fs_fwd.h:362:47: note:   no known conversion for argument 2 from 'std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >' to 'std::error_code&'
  362 |   file_time_type last_write_time(const path&, error_code&) noexcept;
      |                                               ^~~~~~~~~~~
In file included from /usr/local/include/c++/9.1.0/filesystem:39,
                 from test.cpp:1:
/usr/local/include/c++/9.1.0/bits/fs_ops.h:245:8: note: candidate: 'void std::filesystem::last_write_time(const std::filesystem::__cxx11::path&, std::filesystem::file_time_type)'
  245 |   void last_write_time(const path& __p, file_time_type __new_time);
      |        ^~~~~~~~~~~~~~~
/usr/local/include/c++/9.1.0/bits/fs_ops.h:245:56: note:   no known conversion for argument 2 from 'time_point<std::chrono::_V2::system_clock,[...]>' to 'time_point<std::filesystem::__file_clock,[...]>'
  245 |   void last_write_time(const path& __p, file_time_type __new_time);
      |                                         ~~~~~~~~~~~~~~~^~~~~~~~~~
/usr/local/include/c++/9.1.0/bits/fs_ops.h:246:8: note: candidate: 'void std::filesystem::last_write_time(const std::filesystem::__cxx11::path&, std::filesystem::file_time_type, std::error_code&)'
  246 |   void last_write_time(const path& __p, file_time_type __new_time,
      |        ^~~~~~~~~~~~~~~
/usr/local/include/c++/9.1.0/bits/fs_ops.h:246:8: note:   candidate expects 3 arguments, 2 provided

shell returned 1

Press ENTER or type command to continue

Compile command: g++ -std=c++17 test.cpp -lstdc++-fs (even though linking stdc++-fs is unnecessary on since g++9)

My question is - what is the idiomatic use of this function the way I intended? Namely changing the last write time of a file to five minutes ago.

I understand I used this in somehow non-idiomatic way if a breaking change was introduced.

like image 740
Leśny Rumcajs Avatar asked Dec 13 '22 11:12

Leśny Rumcajs


2 Answers

As pointed out by @LightnessRacesinOrbit in their answer the std::filesystem::file_time_type that last_write_time takes uses an unspecified time_point type. This means it is completely legal for this to break moving from one compiler to another or even versions of the same compiler.

What you can do though is get the clock that the implementation uses, and use that yourself. std::chrono::time_point was built to take the clock type that creates it as a template parameter, and it surfaces a public clock type that represents that. So to portably get the clock, and call now on it you can use

std::filesystem::last_write_time("test", std::filesystem::file_time_type::clock::now() - 5min);
//                                       ^ give me whatever clock you use and call now on it
like image 69
NathanOliver Avatar answered Jan 19 '23 21:01

NathanOliver


This is touched on by cppreference.com's example.

Your giving a std::chrono::time_point<std::chrono::system_clock> to std::file_time_type was never portable; it just happened to work on your previous toolchain. That wasn't really your fault: you were unavoidably relying on implementation details.

C++20 has introduced a portable alternative, so you'd do:

std::filesystem::last_write_time("test", std::chrono::file_clock::now() - 5min);
//                                                    ^^^^^^^^^^

… i.e. use file_clock, rather than just hoping that the alternative is convertible to whatever implementation-defined clock is used by your toolchain.

Whether that's supported in GCC 9 I couldn't say for sure, though it doesn't look like it. If there's a workaround, I'm not aware of one (which is why the C++20 change was made).

This problem affects other people on other toolchains, too.

like image 42
Lightness Races in Orbit Avatar answered Jan 19 '23 21:01

Lightness Races in Orbit