1. Revision History
1.1. Revision 1 (Draft)
-
Make
be standard layoutnative_handle_type -
Add precondition (
) tois_open () == true. native_handle () -
Add feature test macro
__cpp_lib_fstream_native_handle -
Fix errors with opening the file with POSIX APIs in Motivation (see, we need this paper, fstreams are easier to open correctly!)
-
Add additional motivating use case in vectored/scatter-gather IO
-
->Regular regular
Incorporate LEWGI feedback from Cologne (July 2019):
-
Move to a member function and member typedef
-
Make
return value not be mandated to be uniquenative_handle -
Add note about how the presence of the members is required, and not implementation-defined (like for thread)
LEWGI Polls from Cologne:
Attendance: 21
We should promise more committee time pursuing P1759, knowing that our time is scarce and this will leave less time for other work.
SF | F | N | A | SA |
3 | 7 | 6 | 1 | 0 |
Knowing what we know now, we should promise more committee time to pursuing a unifying native handle type for the standard library, knowing that our time is scarce and this will leave less time for other work.
SF | F | N | A | SA |
0 | 0 | 3 | 6 | 5 |
SA: Implementers had concerns with inability to add new native handle types without breaking ABI.
Member function (MF) vs free function (FF) addded to stream classes.
SMF | MF | N | FF | SFF |
2 | 9 | 4 | 1 | 1 |
SFF: Member function bad, free function good
The native handle type and native handle member function should always exist (they may return an invalid handle).
SF | F | N | A | SA |
UNANIMOUS CONSENT |
An implementer had concerns how this paper wouldn’t be useful if native handles were as underspecified as they are with
.
Forward P1759, with a member function API that is always defined, to LEWG for C++23, knowing that our time is scarce and this will leave less time for other work.
SF | F | N | A | SA |
3 | 10 | 2 | 2 | 0 |
1.2. Revision 0
Initial revision.2. Overview
This paper proposes adding a new typedef to standard file streams:
.
This type is an alias to whatever type the platform uses for its file descriptors:
on POSIX,
(
) on Windows, and something else on other platforms.
This type is a non-owning handle and is to be small,
, standard layout, and trivially copyable.
Alongside this, this paper proposes adding a concrete member function:
,
returning a
, to the following class templates:
-
basic_filebuf -
basic_ifstream -
basic_ofstream -
basic_fstream
3. Motivation
For some operations, using OS/platform-specific file APIs is necessary. If this is the case, they are unable to use iostreams without reopening the file with the platform-specific APIs.
For example, if someone wanted to query the time a file was last modified on POSIX, they’d use
, which takes a file descriptor:
int fd = :: open ( "~/foo.txt" , O_RDONLY ); :: stat s {}; int err = :: fstat ( fd , & s ); std :: chrono :: sys_seconds last_modified = std :: chrono :: seconds ( s . st_mtime . tv_sec );
Note: The Filesystem TS introduced the
structure and
function retuning one.
This doesn’t solve our problem, because
takes a path, not a native file descriptor
(using paths is potentially racy),
and
only contains member functions
and
,
not one for last time of modification.
Extending this structure is out of scope for this proposal,
and not feasible for every single possible operation the user may wish to do with OS APIs.
If the user needs to do a single operation not supported by the standard library, they have to make choice between only using OS APIs, or reopening the file every time necessary, likely forgetting to close the file, or running into buffering or synchronization issues.
// Writing the latest modification date to a file std :: chrono :: sys_seconds last_modified ( int fd ) { // See above } // Today’s code // Option #1: // Use iostreams by reopening the file { int fd = :: open ( "~/foo.txt" , O_RDONLY ); // CreateFile on Windows auto lm = last_modified ( fd ); :: close ( fd ); // CloseFile on Windows // Hope the path still points to the file! // Need to allocate std :: ofstream of ( "~/foo.txt" ); of << std :: chrono :: format ( "%c" , lm ) << '\n' ; // Need to flush } // Option #2: // Abstain from using iostreams altogether { int fd = :: open ( "~/foo.txt" , O_RDWR ); auto lm = last_modified ( fd ); // Using ::write() is clunky; // skipping error handling for brevity auto str = std :: chrono :: format ( "%c" , lm ); str . push_back ( '\n' ); :: write ( fd , str . data (), str . size ()); // Remember to close! // Hope format or push_back doesn’t throw :: close ( fd ); } // This proposal // No need to use platform-specific APIs to open the file { std :: ofstream of ( "~/foo.txt" ); auto lm = last_modified ( of . native_handle ()); of << std :: chrono :: format ( "%c" , lm ) << '\n' ; // RAII does ownership handling for us }
The utility of getting a file descriptor (or other native file handle) is not limited to getting the last modification date. Other examples include, but are definitely not limited to:
-
file locking (
+fcntl ()
on POSIX,F_SETLK
on Windows)LockFile -
getting file status flags (
+fcntl ()
on POSIX,F_GETFL
on Windows)GetFileInformationByHandle -
vectored/scatter-gather IO (
/vread ()
)vwrite () -
non-blocking IO (
+fcntl ()
/O_NONBLOCK
on POSIX)F_SETSIG
Basically, this paper would make standard file streams interoperable with operating system interfaces, making iostreams more useful in that regard.
An alternative would be adding a lot of this functionality to
and
.
The problem is, that some of this behavior is inherently platform-specific.
For example, getting the inode of a file is something that only makes sense on POSIX,
so cannot be made part of the
interface, and is only accessible through the native file descriptor.
Facilities replacing iostreams, although desirable, are not going to be available in the standard in the near future. The author, alongside many others, would thus find this functionality useful.
4. Scope
This paper does not propose constructing a file stream or stream buffer from a native file handle. The author is worried of ownership and implementation issues possibly associated with this design.
// NOT PROPOSED #include <fstream>#include <fcntl.h>auto fd = :: open ( /* ... */ ); auto f = std :: fstream { fd };
This paper also does not touch anything related to
.
5. Design Discussion
See polls in §1 Revision History for more details
5.1. Type of native_handle_type
In this paper, the definition for
is much more strict than in
.
For reference, this is the wording from 32.2.3 Native handles [thread.req.native], from [N4800]:
Several classes described in this Clause have membersand
native_handle_type . The presence of these members and their semantics is implementation-defined. [ Note: These members allow implementations to provide access to implementation details. Their names are specified to facilitate portable compile-time detection. Actual use of these members is inherently non-portable. — end note ]
native_handle
During the review of R0 of this paper in Cologne by LEWGI, an implementor mentioned how having the same specification here would make this paper effectively useless. Having the presence of a member be implementation-defined without a feature detect macro was deemed as bad design, which should not be replicated in this paper.
The proposed alternative in this paper, as directed by LEWGI, is allowing a conforming implementation to return an invalid native file handle, if it cannot be retrieved.
5.2. Member function vs free function
R0 of this paper had a free function
instead of a member function,
due to possible ABI-related concerns.
This turned out not to be an issue, so the design was changed to be a member function, for consistency with
.
5.3. Precondition
The member function
, as specified in this paper, has a precondition of
.
The precondition is specified with "Expects", so breaking it would be UB, and in practice enforced with an assert.
An alternative to this would be throwing if the file is not open, or returning some unspecified invalid handle.
6. Impact On the Standard and Existing Code
This proposal is a pure library extension, requiring no changes to the core language. It would cause no existing conforming code to break.
7. Implementation
Implementing this paper should be a relatively trivial task.
Although all implementations surveyed (libstdc++, libc++ and MSVC) use
instead of native file descriptors in their
implementations,
these platforms provide facilites to get a native handle from a
;
on POSIX, and
+
on Windows.
The following reference implementations use these.
For libstdc++ on Linux:
template < class CharT , class Traits > class basic_filebuf { // ... using native_handle_type = int ; // ... native_handle_type native_handle () { assert ( is_open ()); // _M_file (__basic_file<char>) has a member function for this purpose return _M_file . fd (); // ::fileno(file.file()) could also be used } // ... } // (i|o)fstream implementation is trivial with rdbuf()
For libc++ on Linux:
template < class CharT , class Traits > class basic_filebuf { // ... using native_handle_type = int ; // ... native_handle_type native_handle () { assert ( is_open ()); // __file_ is a FILE* return :: fileno ( __file_ ) } // ... } // (i|o)fstream implementation is trivial with rdbuf()
For MSVC:
template < class CharT , class Traits > class basic_filebuf { // ... using native_handle_type = HANDLE ; // ... native_handle_type native_handle () { assert ( is_open ()); // _Myfile is a FILE* auto cfile = :: _fileno ( _Myfile ); // _get_osfhandle returns intptr_t, HANDLE is a void* return static_cast < HANDLE > ( :: _get_osfhandle ( cfile )); } // ... } // (i|o)fstream implementation is trivial with rdbuf()
8. Prior Art
[Boost.IOStreams] provides
,
, and
, which,
when used in conjunction with
, are
s using a file descriptor.
These classes can be constructed from a path or a native handle (
or
) and can also return it with member function
.
The Networking TS [N4734] has members
and
in numerous places, including
.
It specifies (in [socket.reqmts.native]) the presence of these members in a similar fashion to
,
as in making their presence implementation-defined.
It does, however, recommend POSIX-based systems to use
for this purpose.
Niall Douglas’s [P1031] also defined a structure
with an extensive interface and a member
with an
and a
, with a constructor taking either one of these.
8.1. Discussion
There has been some discussion over the years about various things relating to this issue, but as far as the author is aware, no concrete proposal has ever been submitted.
There have been a number of threads on std-discussion and std-proposals: [std-proposals-native-handle], [std-discussion-fd-io], [std-proposals-native-raw-io], [std-proposals-fd-access]. The last one of these lead to a draft paper, that was never submitted: [access-file-descriptors].
The consensus that the author took from these discussions is, that native handle support for iostreams would be very much welcome.
An objection was raised by Billy O’Neal to being able to retrieve a native file handle from a standard file stream:
[This] also would need to mandate that the C++ streams be implemented directly such that there was a 1:1 native handle relationship, which may not be the case. For instance, a valid implementation of C++ iostreams would be on top of cstdio, which would not have any kind of native handle to expose.– Billy O’Neal: [std-proposals-billy-oneal]
Every implementation surveyed did implement
on top of C stdio, but these platforms also provide functionality for getting a file descriptor out of a
.
On every platform, file I/O is ultimately implemented on top of native APIs, so not providing access to a file descriptor from a
would be rather unfortunate.
Should such a platform exist, they probably don’t have a conforming C++ implementation anyway.
See §7 Implementation for more.
Additionally, as directed by LEWGI,
can just return an invalid handle,
if the implementation really can’t get a valid one corresponding to the file.
8.2. Existing precendent for presence of native_handle
Types with a standard way of getting the native handle | Types without a standard way of getting the native handle |
Proposals:
|
|
9. Technical Specifications
The wording is based on [N4800].
Add the following row into Table 36: Standard library feature-test macros [tab:support.ft] in [support.limits.general]:Add the following subsection (?) into File-based streams [file.streams], after [fstream.syn].
__cpp_lib_filesystem 201703L <filesystem> __cpp_lib_fstream_native_handle TBD <fstream> __cpp_lib_gcd_lcm 201606L <numeric>
Note to editor: Replace ? with the appropriate section number.
Add the following into Class template? Native handles [file.native]
Several classes described in this section have a member.
native_handle_type The type
serves as a type representing a platform-specific handle to a file. It satisfies the requirements of
native_handle_type , and is trivially copyable and standard layout.
regular [ Note: For operating systems based on POSIX,
should be
native_handle_type . For Windows-based operating systems, it should be
int . — end note ]
HANDLE
basic_filebuf
[filebuf]:
namespace std { template < class charT , class traits = char_traits < charT >> class basic_filebuf : public basic_streambuf < charT , traits > { public : // Note: Add after other member typedefs using native_handle_type = implementation - defined ; // see [file.native] // Note: Add as the last [filebuf.members] native_handle_type native_handle (); // ... } }
Modify paragraph § 1 of Class template
[filebuf]:
The classAdd the following to the end of Member functions [filebuf.members]:associates both the input sequence and the output sequence with a file. The file has an associated
basic_filebuf < charT , traits > . Whether the associated
native_handle_type is unique for each instance of a
native_handle_type , is implementation-defined. [ Note: This differs from thread native handles [thread.req.native], the presence of which is implementation-defined. — end note ]
basic_filebuf
Add the following into Class templatenative_handle_type native_handle (); Expects:
is
is_open () true
.Throws: Nothing.
Returns: The
associated with the underlying file of
native_handle_type .
* this
basic_ifstream
[ifstream]:
Add the following to the end of Member functions [ifstream.members]:namespace std { template < class charT , class traits = char_traits < charT >> class basic_ifstream : public basic_istream < charT , traits > { public : // Note: Add after other member typedefs using native_handle_type = typename basic_filebuf < charT , traits >:: native_handle_type ; // Note: Add as the last [ifstream.members] native_handle_type native_handle (); // ... } }
Add the following into Class templatenative_handle_type native_handle (); Effects: Equivalent to:
.
return rdbuf () -> native_handle ();
basic_ofstream
[ofstream]:
Add the following to the end of Member functions [ofstream.members]:namespace std { template < class charT , class traits = char_traits < charT >> class basic_ofstream : public basic_ostream < charT , traits > { public : // Note: Add after other member typedefs using native_handle_type = typename basic_filebuf < charT , traits >:: native_handle_type ; // Note: Add as the last [ofstream.members] native_handle_type native_handle (); // ... } }
Add the following into Class templatenative_handle_type native_handle (); Effects: Equivalent to:
.
return rdbuf () -> native_handle ();
basic_fstream
[fstream]:
Add the following to the end of Member functions [fstream.members]:namespace std { template < class charT , class traits = char_traits < charT >> class basic_fstream : public basic_iostream < charT , traits > { public : // Note: Add after other member typedefs using native_handle_type = typename basic_filebuf < charT , traits >:: native_handle_type ; // Note: Add as the last [fstream.members] native_handle_type native_handle (); // ... } }
native_handle_type native_handle (); Effects: Equivalent to:
.
return rdbuf () -> native_handle ();
10. Acknowledgements
Thanks to Niall Douglas for feedback, encouragement and ambitious suggestions for this paper.
Thanks to the rest of the co-authors of [P1750] for the idea after cutting this functionality out, especially to Jeff Garland for providing a heads-up about a possible ABI-break that I totally would’ve missed, even though it ended up being a non-issue.