Skip to content

Commit b451fcb

Browse files
authored
Implement tmp::basic_file (#158)
Implemented `basic_file` template instead of `file` class Closes #195
1 parent 489afac commit b451fcb

File tree

3 files changed

+104
-74
lines changed

3 files changed

+104
-74
lines changed

.github/workflows/windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ name: windows
66
pull_request:
77

88
env:
9-
CXXFLAGS: /WX /W3 /wd4251
9+
CXXFLAGS: /WX /W3 /wd4244
1010

1111
jobs:
1212
cmake:

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

tests/file.cpp

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,79 @@
2525
namespace tmp {
2626
namespace {
2727

28-
/// 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());
31-
return filebuf != nullptr && filebuf->is_open();
32-
}
28+
/// Test fixture for `basic_file` tests
29+
template<class charT> class file : public testing::Test {
30+
public:
31+
/// Returns whether the file device object is open
32+
bool is_open(const std::basic_streambuf<charT>* streambuf) {
33+
auto filebuf = dynamic_cast<const std::basic_filebuf<charT>*>(streambuf);
34+
return filebuf != nullptr && filebuf->is_open();
35+
}
3336

34-
/// Checks if the given file handle is valid
35-
/// @param handle handle to check
36-
/// @returns whether the handle is valid
37-
bool is_open(file::native_handle_type handle) {
37+
/// Returns whether the given file handle is open
38+
bool is_open(typename basic_file<charT>::native_handle_type handle) {
3839
#ifdef _WIN32
39-
BY_HANDLE_FILE_INFORMATION info;
40-
return GetFileInformationByHandle(handle, &info);
40+
BY_HANDLE_FILE_INFORMATION info;
41+
return GetFileInformationByHandle(handle, &info);
4142
#else
42-
return fcntl(handle, F_GETFD) != -1;
43+
return fcntl(handle, F_GETFD) != -1;
4344
#endif
44-
}
45+
}
46+
47+
/// Converts the string to the `charT` string type
48+
constexpr std::basic_string<charT> convert_string(std::string_view string) {
49+
std::basic_string<charT> result;
50+
51+
for (char character : string) {
52+
result += static_cast<charT>(character);
53+
}
54+
55+
return result;
56+
}
57+
};
58+
59+
/// Name generator for typed test suite
60+
struct name_generator {
61+
template<typename T> static std::string GetName(int) {
62+
return typeid(T).name();
63+
}
64+
};
65+
66+
using char_types = testing::Types<char, wchar_t>;
67+
TYPED_TEST_SUITE(file, char_types, name_generator);
4568

4669
/// 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>);
70+
TYPED_TEST(file, type_traits) {
71+
using traits = std::char_traits<TypeParam>;
72+
using file_t = basic_file<TypeParam>;
73+
using testing::StaticAssertTypeEq;
74+
75+
static_assert(std::is_base_of_v<std::basic_iostream<TypeParam>, file_t>);
76+
StaticAssertTypeEq<typename file_t::char_type, TypeParam>();
77+
StaticAssertTypeEq<typename file_t::traits_type, traits>();
78+
StaticAssertTypeEq<typename file_t::int_type, typename traits::int_type>();
79+
StaticAssertTypeEq<typename file_t::pos_type, typename traits::pos_type>();
80+
StaticAssertTypeEq<typename file_t::off_type, typename traits::off_type>();
5681
}
5782

5883
/// Tests file creation
59-
TEST(file, create) {
60-
file tmpfile = file();
61-
EXPECT_TRUE(is_open(tmpfile));
62-
EXPECT_TRUE(is_open(tmpfile.native_handle()));
84+
TYPED_TEST(file, create) {
85+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
86+
EXPECT_TRUE(TestFixture::is_open(tmpfile.rdbuf()));
87+
EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle()));
6388
}
6489

6590
/// Tests multiple file creation
66-
TEST(file, create_multiple) {
67-
file fst = file();
68-
file snd = file();
91+
TYPED_TEST(file, create_multiple) {
92+
basic_file<TypeParam> fst = basic_file<TypeParam>();
93+
basic_file<TypeParam> snd = basic_file<TypeParam>();
6994

7095
EXPECT_NE(fst.native_handle(), snd.native_handle());
7196
}
7297

7398
/// Tests file open mode
74-
TEST(file, openmode) {
75-
file tmpfile = file();
99+
TYPED_TEST(file, openmode) {
100+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
76101
tmpfile << "Hello, World!" << std::flush;
77102
tmpfile.seekg(0);
78103
tmpfile << "Goodbye!" << std::flush;
@@ -83,72 +108,72 @@ TEST(file, openmode) {
83108
}
84109

85110
/// Tests that destructor removes a file
86-
TEST(file, destructor) {
87-
file::native_handle_type handle;
111+
TYPED_TEST(file, destructor) {
112+
typename basic_file<TypeParam>::native_handle_type handle;
88113

89114
{
90-
file tmpfile = file();
115+
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
91116
handle = tmpfile.native_handle();
92117
}
93118

94-
EXPECT_FALSE(is_open(handle));
119+
EXPECT_FALSE(TestFixture::is_open(handle));
95120
}
96121

97122
/// Tests file move constructor
98-
TEST(file, move_constructor) {
99-
file fst = file();
123+
TYPED_TEST(file, move_constructor) {
124+
basic_file<TypeParam> fst = basic_file<TypeParam>();
100125
fst << "Hello!";
101126

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

104-
EXPECT_TRUE(is_open(snd));
129+
EXPECT_TRUE(TestFixture::is_open(snd.rdbuf()));
105130

106131
snd.seekg(0);
107-
std::string content;
132+
std::basic_string<TypeParam> content;
108133
snd >> content;
109-
EXPECT_EQ(content, "Hello!");
134+
EXPECT_EQ(content, TestFixture::convert_string("Hello!"));
110135
}
111136

112137
/// Tests file move assignment operator
113-
TEST(file, move_assignment) {
114-
file fst = file();
138+
TYPED_TEST(file, move_assignment) {
139+
basic_file<TypeParam> fst = basic_file<TypeParam>();
115140

116141
{
117-
file snd = file();
142+
basic_file<TypeParam> snd = basic_file<TypeParam>();
118143
snd << "Hello!";
119144

120-
file::native_handle_type fst_handle = fst.native_handle();
121-
file::native_handle_type snd_handle = snd.native_handle();
145+
typename decltype(fst)::native_handle_type fst_handle = fst.native_handle();
146+
typename decltype(snd)::native_handle_type snd_handle = snd.native_handle();
122147

123148
fst = std::move(snd);
124149

125-
EXPECT_FALSE(is_open(fst_handle));
126-
EXPECT_TRUE(is_open(snd_handle));
150+
EXPECT_FALSE(TestFixture::is_open(fst_handle));
151+
EXPECT_TRUE(TestFixture::is_open(snd_handle));
127152
EXPECT_EQ(fst.native_handle(), snd_handle);
128153
}
129154

130-
EXPECT_TRUE(is_open(fst));
155+
EXPECT_TRUE(TestFixture::is_open(fst.rdbuf()));
131156

132157
fst.seekg(0);
133-
std::string content;
158+
std::basic_string<TypeParam> content;
134159
fst >> content;
135-
EXPECT_EQ(content, "Hello!");
160+
EXPECT_EQ(content, TestFixture::convert_string("Hello!"));
136161
}
137162

138163
/// Tests file swapping
139-
TEST(file, swap) {
140-
file fst = file();
141-
file snd = file();
164+
TYPED_TEST(file, swap) {
165+
basic_file<TypeParam> fst = basic_file<TypeParam>();
166+
basic_file<TypeParam> snd = basic_file<TypeParam>();
142167

143-
file::native_handle_type fst_handle = fst.native_handle();
144-
file::native_handle_type snd_handle = snd.native_handle();
168+
typename decltype(fst)::native_handle_type fst_handle = fst.native_handle();
169+
typename decltype(snd)::native_handle_type snd_handle = snd.native_handle();
145170

146171
std::swap(fst, snd);
147172

148173
EXPECT_EQ(fst.native_handle(), snd_handle);
149174
EXPECT_EQ(snd.native_handle(), fst_handle);
150-
EXPECT_TRUE(is_open(fst));
151-
EXPECT_TRUE(is_open(snd));
175+
EXPECT_TRUE(TestFixture::is_open(fst.rdbuf()));
176+
EXPECT_TRUE(TestFixture::is_open(snd.rdbuf()));
152177
}
153178
} // namespace
154179
} // namespace tmp

0 commit comments

Comments
 (0)