3430. std::fstream & co. should be constructible from string_view

Section: 31.10.1 [fstream.syn] Status: C++23 Submitter: Jonathan Wakely Opened: 2020-04-15 Last modified: 2023-11-22

Priority: 3

View all issues with C++23 status.

Discussion:

We have:

basic_fstream(const char*, openmode);
basic_fstream(const filesystem::path::value_type*, openmode); // wide systems only
basic_fstream(const string&, openmode);
basic_fstream(const filesystem::path&, openmode);

I think the omission of a string_view overload was intentional, because the underlying OS call (such as fopen) needs a NTBS. We wanted the allocation required to turn a string_view into an NTBS to be explicitly requested by the user. But then we added the path overload, which is callable with a string_view. Converting to a path is more expensive than converting to std::string, because a path has to at least construct a basic_string, and potentially also does an encoding conversion, parses the path, and potentially allocates a sequence of path objects for the path components.

This means the simpler, more obvious code is slower and uses more memory:

string_view sv = "foo.txt";
fstream f1(sv); // bad
fstream f2(string(sv)); // good

We should just allow passing a string_view directly, since it already compiles but doesn't do what anybody expects or wants.

Even with a string_view overload, passing types like const char16_t* or u32string_view will still implicitly convert to filesystem::path, but that seems reasonable. In those cases the encoding conversion is necessary. For Windows we support construction from const wchar_t* but not from wstring or wstring_view, which means those types will convert to filesystem::path. That seems suboptimal, so we might also want to add wstring and wstring_view overloads for "wide systems only", as per 31.10.1 [fstream.syn] p3.

Daniel:

LWG 2883 has a more general view on that but does not consider potential cost differences in the presence of path overloads (Which didn't exist at this point yet).

[2020-05-09; Reflector prioritization]

Set priority to 3 after reflector discussions.

[2020-08-10; Jonathan comments]

An alternative fix would be to retain the original design and not allow construction from a string_view. The path parameters could be changed to template parameters which are constrained to be exactly path, and not things like string_view which can convert to path.

[2020-08-21; Issue processing telecon: send to LEWG]

Just adding support for string_view doesn't prevent expensive conversions from other types that convert to path. Preference for avoiding all expensive implicit conversions to path, maybe via abbreviated function templates:

basic_fstream(same_as<filesystem::path> auto const&, openmode);

It's possible path_view will provide a better option at some point.

It was noted that 2676 did intentionally allow conversions from "strings of character types wchar_t, char16_t, and char32_t". Those conversions don't need to be implicit for that to be supported.

[2020-09-11; Tomasz comments and provides wording]

During the LEWG 2020-08-24 telecon the LEWG provided following guidance on the issue:

We took one poll (exact wording in the notes) to constrain the constructor which takes filesystem::path to only take filesystem::path and not things convertible to it, but only 9 out of 26 people present actually voted. Our interpretation: LWG should go ahead with making this change. There is still plenty of time for someone who hasn't yet commented on this to bring it up even if it is in a tentatively ready state. It would be nice to see a paper to address the problem of the templated path constructor, but no one has yet volunteered to do so. Note: the issue description is now a misnomer, as adding a string_view constructor is no longer being considered at this time.

The proposed P/R follows original LWG proposal and makes the path constructor of the basic_*fstreams "explicit". To adhere to current policy, we refrain from use of requires clauses and abbreviated function syntax, and introduce a Constraints element.

[2021-05-21; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

[2021-06-07 Approved at June 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4861.

  1. Modify 31.10.4 [ifstream], class template basic_ifstream synopsis, as indicated:

    […]
    explicit basic_ifstream(const string& s,
                            ios_base::openmode mode = ios_base::in);
    template<class T>
    explicit basic_ifstream(const filesystem::pathT& s,
                            ios_base::openmode mode = ios_base::in);
    […]
    
  2. Modify 31.10.4.2 [ifstream.cons] as indicated:

    explicit basic_ifstream(const string& s,
                            ios_base::openmode mode = ios_base::in);
    

    -?- Effects: Equivalent to: basic_ifstream(s.c_str(), mode).

    template<class T>
    explicit basic_ifstream(const filesystem::pathT& s,
                            ios_base::openmode mode = ios_base::in);
    

    -?- Constraints: is_same_v<T, filesystem::path> is true.

    -3- Effects: Equivalent to: basic_ifstream(s.c_str(), mode).

  3. Modify 31.10.5 [ofstream], class template basic_ofstream synopsis, as indicated:

    […]
    explicit basic_ofstream(const string& s,
                            ios_base::openmode mode = ios_base::out);
    template<class T>
    explicit basic_ofstream(const filesystem::pathT& s,
                            ios_base::openmode mode = ios_base::out);
    […]
    
  4. Modify 31.10.5.2 [ofstream.cons] as indicated:

    explicit basic_ofstream(const string& s,
                            ios_base::openmode mode = ios_base::out);
    

    -?- Effects: Equivalent to: basic_ofstream(s.c_str(), mode).

    template<class T>
    explicit basic_ofstream(const filesystem::pathT& s,
                            ios_base::openmode mode = ios_base::out);
    

    -?- Constraints: is_same_v<T, filesystem::path> is true.

    -3- Effects: Equivalent to: basic_ofstream(s.c_str(), mode).

  5. Modify 31.10.6 [fstream], class template basic_fstream synopsis, as indicated:

    […]
    explicit basic_fstream(
      const string& s,
      ios_base::openmode mode = ios_base::in | ios_base::out);
    template<class T>
    explicit basic_fstream(
      const filesystem::pathT& s,
      ios_base::openmode mode = ios_base::in | ios_base::out);
    […]
    
  6. Modify 31.10.6.2 [fstream.cons] as indicated:

    explicit basic_fstream(
      const string& s,
      ios_base::openmode mode = ios_base::in | ios_base::out);
    

    -?- Effects: Equivalent to: basic_fstream(s.c_str(), mode).

    template<class T>
    explicit basic_fstream(
      const filesystem::pathT& s,
      ios_base::openmode mode = ios_base::in | ios_base::out);
    

    -?- Constraints: is_same_v<T, filesystem::path> is true.

    -3- Effects: Equivalent to: basic_fstream(s.c_str(), mode).