Skip to content

Commit b4b624f

Browse files
committed
Implement basic_file
1 parent 489afac commit b4b624f

File tree

3 files changed

+101
-54
lines changed

3 files changed

+101
-54
lines changed

include/tmp/file

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,37 +64,38 @@ extern "C++" file_native_handle get_native_handle(std::FILE* file) noexcept;
6464
/// }
6565
/// }
6666
/// @endcode
67-
class file : public std::iostream {
67+
template<class charT, class traits = std::char_traits<charT>>
68+
class basic_file : public std::basic_iostream<charT, traits> {
6869
public:
6970
/// Implementation-defined handle type to the file
7071
using native_handle_type = file_native_handle;
7172

7273
/// Creates and opens a binary temporary file as if by POSIX `tmpfile`
7374
/// @throws std::filesystem::filesystem_error if cannot create a file
74-
explicit file()
75-
: std::iostream(std::addressof(sb)),
75+
explicit basic_file()
76+
: std::basic_iostream<charT, traits>(std::addressof(sb)),
7677
underlying(create_file(), &std::fclose),
7778
sb(open_filebuf(underlying.get())) {}
7879

79-
file(const file&) = delete;
80-
file& operator=(const file&) = delete;
80+
basic_file(const basic_file&) = delete;
81+
basic_file& operator=(const basic_file&) = delete;
8182

8283
/// Moves the ownership of the file managed by `other` to a new handle
8384
/// @note After the move, `other` is not associated with a file
8485
/// @param other Another file that will be moved from
85-
file(file&& other) noexcept
86-
: std::iostream(std::move(other)),
86+
basic_file(basic_file&& other) noexcept
87+
: std::basic_iostream<charT, traits>(std::move(other)),
8788
underlying(std::move(other.underlying)),
8889
sb(std::move(other.sb)) {
89-
set_rdbuf(std::addressof(sb));
90+
std::basic_iostream<charT, traits>::set_rdbuf(std::addressof(sb));
9091
}
9192

9293
/// Deletes and closes the managed temporary file, then moves the ownership
9394
/// of the file managed by `other` to `this`
9495
/// @note After the assignment, `other` is not associated with a file
9596
/// @param other Another file that will be moved from
9697
/// @returns `*this`
97-
file& operator=(file&& other) = default;
98+
basic_file& operator=(basic_file&& other) = default;
9899

99100
/// Returns an implementation-defined handle to this file
100101
/// @returns The underlying implementation-defined handle
@@ -103,32 +104,33 @@ public:
103104
}
104105

105106
/// Closes and deletes this file
106-
~file() noexcept override = default;
107+
~basic_file() noexcept override = default;
107108

108109
private:
109110
/// The underlying C file stream
110111
std::unique_ptr<std::FILE, int (*)(std::FILE*)> underlying;
111112

112113
#ifdef __GLIBCXX__
113114
/// The underlying raw file device object
114-
mutable __gnu_cxx::stdio_filebuf<char> sb;
115+
mutable __gnu_cxx::stdio_filebuf<charT, traits> sb;
115116
#else
116117
/// The underlying raw file device object
117-
std::filebuf sb;
118+
std::basic_filebuf<charT, traits> sb;
118119
#endif
119120

120121
/// Returns a file device for the given file stream
121122
/// @param file The file stream
122123
/// @returns The new file device
123124
/// @throws std::filesystem::filesystem_error if cannot open the file stream
124125
decltype(sb) open_filebuf(std::FILE* file) {
126+
constexpr auto mode = std::ios::binary | std::ios::in | std::ios::out;
125127
decltype(sb) sb;
126128
#if defined(_MSC_VER)
127-
sb = std::filebuf(file);
129+
sb = std::basic_filebuf<charT, traits>(file);
128130
#elif defined(_LIBCPP_VERSION)
129-
sb.__open(get_native_handle(file), binary | in | out);
131+
sb.__open(get_native_handle(file), mode);
130132
#else
131-
sb = __gnu_cxx::stdio_filebuf<char>(file, binary | in | out);
133+
sb = __gnu_cxx::stdio_filebuf<charT, traits>(file, mode);
132134
#endif
133135

134136
if (!sb.is_open()) {
@@ -140,6 +142,9 @@ private:
140142
return sb;
141143
}
142144
};
145+
146+
using file = basic_file<char>;
147+
using wfile = basic_file<wchar_t>;
143148
} // namespace tmp
144149

145150
#endif // TMP_FILE_H

src/file.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99

1010
#include <cstdio>
1111
#include <filesystem>
12+
#include <ios>
1213
#include <system_error>
1314
#include <type_traits>
1415

16+
#ifdef __GLIBCXX__
17+
#include <ext/stdio_filebuf.h>
18+
#endif
19+
1520
#ifdef _WIN32
1621
#include <Windows.h>
1722
#include <io.h>

tests/file.cpp

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ namespace tmp {
2626
namespace {
2727

2828
/// Returns whether the underlying raw file device object is open
29-
bool is_open(const file& file) {
30-
std::filebuf* filebuf = dynamic_cast<std::filebuf*>(file.rdbuf());
29+
template<class charT, class traits>
30+
bool is_open(const basic_file<charT, traits>& file) {
31+
auto filebuf = dynamic_cast<std::basic_filebuf<charT, traits>*>(file.rdbuf());
3132
return filebuf != nullptr && filebuf->is_open();
3233
}
3334

@@ -43,36 +44,68 @@ bool is_open(file::native_handle_type handle) {
4344
#endif
4445
}
4546

47+
template<typename charT>
48+
constexpr std::basic_string<charT> convert_string(const char* string) {
49+
if constexpr (std::is_same_v<charT, char>) {
50+
return std::basic_string<charT>(string);
51+
}
52+
53+
if constexpr (std::is_same_v<charT, wchar_t>) {
54+
std::mbstate_t state = std::mbstate_t();
55+
56+
std::basic_string<charT> result;
57+
result.resize(std::mbsrtowcs(nullptr, &string, 0, &state));
58+
59+
std::size_t ret =
60+
std::mbsrtowcs(result.data(), &string, result.size(), &state);
61+
assert(ret == result.size());
62+
return result;
63+
}
64+
65+
throw std::invalid_argument("Unknown character type");
66+
}
67+
68+
template<typename charT> class file : public testing::Test {};
69+
70+
using char_types = testing::Types<char, wchar_t>;
71+
TYPED_TEST_SUITE(file, char_types, testing::internal::DefaultNameGenerator);
72+
4673
/// Tests file type traits and member types
47-
TEST(file, type_traits) {
48-
using traits = std::char_traits<char>;
49-
50-
static_assert(std::is_base_of_v<std::basic_iostream<char>, file>);
51-
static_assert(std::is_same_v<file::char_type, char>);
52-
static_assert(std::is_same_v<file::traits_type, traits>);
53-
static_assert(std::is_same_v<file::int_type, traits::int_type>);
54-
static_assert(std::is_same_v<file::pos_type, traits::pos_type>);
55-
static_assert(std::is_same_v<file::off_type, traits::off_type>);
74+
TYPED_TEST(file, type_traits) {
75+
using traits = std::char_traits<TypeParam>;
76+
77+
static_assert(
78+
std::is_base_of_v<std::basic_iostream<TypeParam>, basic_file<TypeParam>>);
79+
static_assert(
80+
std::is_same_v<typename basic_file<TypeParam>::char_type, TypeParam>);
81+
static_assert(
82+
std::is_same_v<typename basic_file<TypeParam>::traits_type, traits>);
83+
static_assert(std::is_same_v<typename basic_file<TypeParam>::int_type,
84+
typename traits::int_type>);
85+
static_assert(std::is_same_v<typename basic_file<TypeParam>::pos_type,
86+
typename traits::pos_type>);
87+
static_assert(std::is_same_v<typename basic_file<TypeParam>::off_type,
88+
typename traits::off_type>);
5689
}
5790

5891
/// Tests file creation
59-
TEST(file, create) {
60-
file tmpfile = file();
92+
TYPED_TEST(file, create) {
93+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
6194
EXPECT_TRUE(is_open(tmpfile));
6295
EXPECT_TRUE(is_open(tmpfile.native_handle()));
6396
}
6497

6598
/// Tests multiple file creation
66-
TEST(file, create_multiple) {
67-
file fst = file();
68-
file snd = file();
99+
TYPED_TEST(file, create_multiple) {
100+
basic_file<TypeParam> fst = basic_file<TypeParam>();
101+
basic_file<TypeParam> snd = basic_file<TypeParam>();
69102

70103
EXPECT_NE(fst.native_handle(), snd.native_handle());
71104
}
72105

73106
/// Tests file open mode
74-
TEST(file, openmode) {
75-
file tmpfile = file();
107+
TYPED_TEST(file, openmode) {
108+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
76109
tmpfile << "Hello, World!" << std::flush;
77110
tmpfile.seekg(0);
78111
tmpfile << "Goodbye!" << std::flush;
@@ -83,42 +116,44 @@ TEST(file, openmode) {
83116
}
84117

85118
/// Tests that destructor removes a file
86-
TEST(file, destructor) {
87-
file::native_handle_type handle;
119+
TYPED_TEST(file, destructor) {
120+
typename basic_file<TypeParam>::native_handle_type handle;
88121

89122
{
90-
file tmpfile = file();
123+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
91124
handle = tmpfile.native_handle();
92125
}
93126

94127
EXPECT_FALSE(is_open(handle));
95128
}
96129

97130
/// Tests file move constructor
98-
TEST(file, move_constructor) {
99-
file fst = file();
131+
TYPED_TEST(file, move_constructor) {
132+
basic_file<TypeParam> fst = basic_file<TypeParam>();
100133
fst << "Hello!";
101134

102-
file snd = file(std::move(fst));
135+
basic_file<TypeParam> snd = basic_file<TypeParam>(std::move(fst));
103136

104137
EXPECT_TRUE(is_open(snd));
105138

106139
snd.seekg(0);
107-
std::string content;
140+
std::basic_string<TypeParam> content;
108141
snd >> content;
109-
EXPECT_EQ(content, "Hello!");
142+
EXPECT_EQ(content, convert_string<TypeParam>("Hello!"));
110143
}
111144

112145
/// Tests file move assignment operator
113-
TEST(file, move_assignment) {
114-
file fst = file();
146+
TYPED_TEST(file, move_assignment) {
147+
basic_file<TypeParam> fst = basic_file<TypeParam>();
115148

116149
{
117-
file snd = file();
150+
basic_file<TypeParam> snd = basic_file<TypeParam>();
118151
snd << "Hello!";
119152

120-
file::native_handle_type fst_handle = fst.native_handle();
121-
file::native_handle_type snd_handle = snd.native_handle();
153+
typename basic_file<TypeParam>::native_handle_type fst_handle =
154+
fst.native_handle();
155+
typename basic_file<TypeParam>::native_handle_type snd_handle =
156+
snd.native_handle();
122157

123158
fst = std::move(snd);
124159

@@ -130,18 +165,20 @@ TEST(file, move_assignment) {
130165
EXPECT_TRUE(is_open(fst));
131166

132167
fst.seekg(0);
133-
std::string content;
168+
std::basic_string<TypeParam> content;
134169
fst >> content;
135-
EXPECT_EQ(content, "Hello!");
170+
EXPECT_EQ(content, convert_string<TypeParam>("Hello!"));
136171
}
137172

138173
/// Tests file swapping
139-
TEST(file, swap) {
140-
file fst = file();
141-
file snd = file();
142-
143-
file::native_handle_type fst_handle = fst.native_handle();
144-
file::native_handle_type snd_handle = snd.native_handle();
174+
TYPED_TEST(file, swap) {
175+
basic_file<TypeParam> fst = basic_file<TypeParam>();
176+
basic_file<TypeParam> snd = basic_file<TypeParam>();
177+
178+
typename basic_file<TypeParam>::native_handle_type fst_handle =
179+
fst.native_handle();
180+
typename basic_file<TypeParam>::native_handle_type snd_handle =
181+
snd.native_handle();
145182

146183
std::swap(fst, snd);
147184

0 commit comments

Comments
 (0)