-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc++] Implement LWG3430 disallow implicit conversion of the source arguments to std::filesystem::path
when constructing std::basic_*fstream
#85079
Conversation
… from string_view Signed-off-by: yronglin <yronglin777@gmail.com>
@llvm/pr-subscribers-libcxx Author: None (yronglin) ChangesImplement LWG3430. Full diff: https://github.com/llvm/llvm-project/pull/85079.diff 5 Files Affected:
diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index e00345533b865d..87f60fa5278e70 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -64,7 +64,7 @@
`2818 <https://wg21.link/LWG2818>`__,"``::std::`` everywhere rule needs tweaking","June 2021","|Nothing To Do|",""
`2997 <https://wg21.link/LWG2997>`__,"LWG 491 and the specification of ``{forward_,}list::unique``","June 2021","",""
`3410 <https://wg21.link/LWG3410>`__,"``lexicographical_compare_three_way`` is overspecified","June 2021","|Complete|","17.0","|spaceship|"
-`3430 <https://wg21.link/LWG3430>`__,"``std::fstream`` & co. should be constructible from string_view","June 2021","",""
+`3430 <https://wg21.link/LWG3430>`__,"``std::fstream`` & co. should be constructible from string_view","June 2021","|Complete|","19.0",""
`3462 <https://wg21.link/LWG3462>`__,"§[formatter.requirements]: Formatter requirements forbid use of ``fc.arg()``","June 2021","|Nothing To Do|","","|format|"
`3481 <https://wg21.link/LWG3481>`__,"``viewable_range`` mishandles lvalue move-only views","June 2021","Superseded by `P2415R2 <https://wg21.link/P2415R2>`__","","|ranges|"
`3506 <https://wg21.link/LWG3506>`__,"Missing allocator-extended constructors for ``priority_queue``","June 2021","|Complete|","14.0"
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 776641b347e6c1..5932a42e1b5392 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -78,8 +78,7 @@ public:
basic_ifstream();
explicit basic_ifstream(const char* s, ios_base::openmode mode = ios_base::in);
explicit basic_ifstream(const string& s, ios_base::openmode mode = ios_base::in);
- explicit basic_ifstream(const filesystem::path& p,
- ios_base::openmode mode = ios_base::in); // C++17
+ template<class T> explicit basic_ifstream(const T& s, ios_base::openmode mode = ios_base::in); // Since C++17
basic_ifstream(basic_ifstream&& rhs);
basic_ifstream& operator=(basic_ifstream&& rhs);
@@ -117,8 +116,7 @@ public:
basic_ofstream();
explicit basic_ofstream(const char* s, ios_base::openmode mode = ios_base::out);
explicit basic_ofstream(const string& s, ios_base::openmode mode = ios_base::out);
- explicit basic_ofstream(const filesystem::path& p,
- ios_base::openmode mode = ios_base::out); // C++17
+ template<class T> explicit basic_ofstream(const T& s, ios_base::openmode mode = ios_base::out); // Since C++17
basic_ofstream(basic_ofstream&& rhs);
basic_ofstream& operator=(basic_ofstream&& rhs);
@@ -158,8 +156,8 @@ public:
basic_fstream();
explicit basic_fstream(const char* s, ios_base::openmode mode = ios_base::in|ios_base::out);
explicit basic_fstream(const string& s, ios_base::openmode mode = ios_base::in|ios_base::out);
- explicit basic_fstream(const filesystem::path& p,
- ios_base::openmode mode = ios_base::in|ios_base::out); C++17
+ template<class T>
+ explicit basic_fstream(const T& s, ios_base::openmode mode = ios_base::in | ios_base::out); // Since C++17
basic_fstream(basic_fstream&& rhs);
basic_fstream& operator=(basic_fstream&& rhs);
@@ -1043,8 +1041,9 @@ public:
# endif
_LIBCPP_HIDE_FROM_ABI explicit basic_ifstream(const string& __s, ios_base::openmode __mode = ios_base::in);
# if _LIBCPP_STD_VER >= 17
+ template <class _Tp, std::enable_if_t<std::is_same_v<_Tp, std::filesystem::path>>* = nullptr>
_LIBCPP_AVAILABILITY_FILESYSTEM_LIBRARY _LIBCPP_HIDE_FROM_ABI explicit basic_ifstream(
- const filesystem::path& __p, ios_base::openmode __mode = ios_base::in)
+ const _Tp& __p, ios_base::openmode __mode = ios_base::in)
: basic_ifstream(__p.c_str(), __mode) {}
# endif // _LIBCPP_STD_VER >= 17
_LIBCPP_HIDE_FROM_ABI basic_ifstream(basic_ifstream&& __rhs);
@@ -1197,8 +1196,9 @@ public:
_LIBCPP_HIDE_FROM_ABI explicit basic_ofstream(const string& __s, ios_base::openmode __mode = ios_base::out);
# if _LIBCPP_STD_VER >= 17
+ template <class _Tp, std::enable_if_t<std::is_same_v<_Tp, std::filesystem::path>>* = nullptr>
_LIBCPP_AVAILABILITY_FILESYSTEM_LIBRARY _LIBCPP_HIDE_FROM_ABI explicit basic_ofstream(
- const filesystem::path& __p, ios_base::openmode __mode = ios_base::out)
+ const _Tp& __p, ios_base::openmode __mode = ios_base::out)
: basic_ofstream(__p.c_str(), __mode) {}
# endif // _LIBCPP_STD_VER >= 17
@@ -1356,8 +1356,9 @@ public:
ios_base::openmode __mode = ios_base::in | ios_base::out);
# if _LIBCPP_STD_VER >= 17
+ template <class _Tp, std::enable_if_t<std::is_same_v<_Tp, std::filesystem::path>>* = nullptr>
_LIBCPP_AVAILABILITY_FILESYSTEM_LIBRARY _LIBCPP_HIDE_FROM_ABI explicit basic_fstream(
- const filesystem::path& __p, ios_base::openmode __mode = ios_base::in | ios_base::out)
+ const _Tp& __p, ios_base::openmode __mode = ios_base::in | ios_base::out)
: basic_fstream(__p.c_str(), __mode) {}
# endif // _LIBCPP_STD_VER >= 17
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/path.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/path.pass.cpp
index bc75d04740da02..e0e211b4478a7d 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/path.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/path.pass.cpp
@@ -21,11 +21,18 @@
#include <fstream>
#include <filesystem>
#include <cassert>
+#include <type_traits>
+
#include "test_macros.h"
#include "platform_support.h"
namespace fs = std::filesystem;
+struct fake_path {};
+
+static_assert(std::is_constructible_v<std::fstream, fs::path>);
+static_assert(!std::is_constructible_v<std::fstream, fake_path>);
+
int main(int, char**) {
fs::path p = get_temp_file_name();
{
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ifstream.cons/path.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ifstream.cons/path.pass.cpp
index cfbb8419fe1c5b..72030c2983e4b3 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/ifstream.cons/path.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/ifstream.cons/path.pass.cpp
@@ -29,6 +29,11 @@
namespace fs = std::filesystem;
+struct fake_path {};
+
+static_assert(std::is_constructible_v<std::ifstream, fs::path>);
+static_assert(!std::is_constructible_v<std::ifstream, fake_path>);
+
int main(int, char**) {
{
fs::path p;
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/path.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/path.pass.cpp
index 316ed776a48b54..01c90f326a130b 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/path.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/path.pass.cpp
@@ -27,6 +27,11 @@
namespace fs = std::filesystem;
+struct fake_path {};
+
+static_assert(std::is_constructible_v<std::ofstream, fs::path>);
+static_assert(!std::is_constructible_v<std::ofstream, fake_path>);
+
int main(int, char**) {
fs::path p = get_temp_file_name();
{
|
struct fake_path {}; | ||
|
||
static_assert(std::is_constructible_v<std::fstream, fs::path>); | ||
static_assert(!std::is_constructible_v<std::fstream, fake_path>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that these code compiles even without this pull request because fake_path
can't be converted to filesystem::path
: https://godbolt.org/z/1dcsjdeEb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! And thanks for your review! I've refined the test cases, also the title need update.
Note: the issue description is now a misnomer, as adding a string_view constructor is no longer being considered at this time.
Signed-off-by: yronglin <yronglin777@gmail.com>
✅ With the latest revision this PR passed the C/C++ code formatter. |
std::filesystem::path
when constructing std::basic_*fstream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LWG issue does not what the title says. Based on the issue
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.
The accepted resolution avoids the path
to string_view
conversion, but doesn't add the string_view
constructor overload. Based on https://godbolt.org/z/TqoWTxT75 the change would disallow using a string_view
instead of making its usage more efficient. I wonder whether this change in behaviour is intended, especially since the title suggests differently and the LWG-issue doesn't change Annex C. @jwakely is the change in behaviour intended?
@yronglin let's put this patch on hold for now.
libcxx/include/fstream
Outdated
@@ -78,8 +78,7 @@ public: | |||
basic_ifstream(); | |||
explicit basic_ifstream(const char* s, ios_base::openmode mode = ios_base::in); | |||
explicit basic_ifstream(const string& s, ios_base::openmode mode = ios_base::in); | |||
explicit basic_ifstream(const filesystem::path& p, | |||
ios_base::openmode mode = ios_base::in); // C++17 | |||
template<class T> explicit basic_ifstream(const T& s, ios_base::openmode mode = ios_base::in); // Since C++17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template<class T> explicit basic_ifstream(const T& s, ios_base::openmode mode = ios_base::in); // Since C++17 | |
template<class T> | |
explicit basic_ifstream(const T& s, ios_base::openmode mode = ios_base::in); // Since C++17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
libcxx/include/fstream
Outdated
@@ -117,8 +116,7 @@ public: | |||
basic_ofstream(); | |||
explicit basic_ofstream(const char* s, ios_base::openmode mode = ios_base::out); | |||
explicit basic_ofstream(const string& s, ios_base::openmode mode = ios_base::out); | |||
explicit basic_ofstream(const filesystem::path& p, | |||
ios_base::openmode mode = ios_base::out); // C++17 | |||
template<class T> explicit basic_ofstream(const T& s, ios_base::openmode mode = ios_base::out); // Since C++17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template<class T> explicit basic_ofstream(const T& s, ios_base::openmode mode = ios_base::out); // Since C++17 | |
template<class T> | |
explicit basic_ofstream(const T& s, ios_base::openmode mode = ios_base::out); // Since C++17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
static_assert(test_non_convert_to_path<wchar_t>()); | ||
#endif // !TEST_HAS_NO_WIDE_CHARACTERS && !_LIBCPP_HAS_OPEN_WITH_WCHAR | ||
|
||
#if TEST_STD_VER > 17 && defined(__cpp_char8_t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if TEST_STD_VER > 17 && defined(__cpp_char8_t) | |
#ifndef TEST_HAS_NO_CHAR8_T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/path.pass.cpp
Show resolved
Hide resolved
Yes, that was what LWG suggested and LEWG approved. |
Thanks for the confirmation. |
@yronglin Can you mention this change in behavior as a breaking change in the release notes. |
Signed-off-by: yronglin <yronglin777@gmail.com>
Thanks for your review! Sure, I've added a release notes item. |
Signed-off-by: yronglin <yronglin777@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks quite good. For now I'm happy to have this without a "old behaviour switch", but I've asked Louis whether he can test it internally to see whether this breaks things.
libcxx/docs/ReleaseNotes/19.rst
Outdated
@@ -97,7 +97,7 @@ TODO | |||
|
|||
ABI Affecting Changes | |||
--------------------- | |||
TODO | |||
- LWG3430: Disallow implicit conversion of the source arguments to ``std::filesystem::path`` when constructing ``std::basic_*fstream``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is better placed under Deprecations and Removals
.
This text is technically correct. However the target audience of the release notes are typical developers and not people who read LWG issues and papers. So let's improve this text by explaining what it does. For example,
This effectively removes the possibility to directly construct a ``std::basic_fstream`` from a
``std::basic_stringview`` or a C-string, instead you can construct a temporary ``std::basic_string``.
This change has been applied to C++17 and later.
Feel free to write this in your own words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've update release notes wording.
@mordante let me know about this and suggested it could be interesting to test on a large code base to see whether "removing" the |
Signed-off-by: yronglin <yronglin777@gmail.com>
Sure! |
Signed-off-by: yronglin <yronglin777@gmail.com>
Signed-off-by: yronglin <yronglin777@gmail.com>
Signed-off-by: yronglin <yronglin777@gmail.com>
std::filesystem::path
when constructing std::basic_*fstream
std::filesystem::path
when constructing std::basic_*fstream
friendly ping~ |
ping~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take this for a run and let you know next week.
Edit: note to self 125553528
libcxx/docs/ReleaseNotes/19.rst
Outdated
constructing ``std::basic_*fstream``. This effectively removes the possibility to directly construct | ||
a ``std::basic_*fstream`` from a ``std::basic_string_view``, a input-iterator or a C-string, instead |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have trailing whitespace in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I've removed that.
Signed-off-by: yronglin <yronglin777@gmail.com>
Thanks for your review! Awaiting your good news. |
I am seeing a bit of breakage of the exact form we'd expect:
However, this breakage seems surmountable (a few projects broke but not that many). It'll be slightly annoying but not a deal breaker. After reading the LWG issue, my understanding is that the intended usage is to instead construct a Pinging @llvm/libcxx-vendors for awareness but this LGTM. |
Yes. That was the intended usage, which was then broken by adding |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for testing the patch internally @ldionne!
LGTM modulo one minor issue. I'd like to have a quick look after that is resolved.
libcxx/test/std/input.output/file.streams/fstreams/ifstream.cons/path.pass.cpp
Outdated
Show resolved
Hide resolved
Signed-off-by: yronglin <yronglin777@gmail.com>
Seems the CI issue was not caused by this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once the bots come back.
Thanks for your review! |
CI has green and I merged this PR. |
LGTM. Next time when somebody likes to review it before committing, please give them some time to look at the changes. |
Thank you for the review and help, I will do this next time. |
This breaks existing code, e.g. this:
Is the assumption that this will be rarely hit in practice? full diag text
|
explicit basic_fstream(const filesystem::path& p, | ||
ios_base::openmode mode = ios_base::in|ios_base::out); C++17 | ||
template<class T> | ||
explicit basic_fstream(const T& s, ios_base::openmode mode = ios_base::in | ios_base::out); // Since C++17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, https://en.cppreference.com/w/cpp/io/basic_fstream/basic_fstream mentions a const std::filesystem::path::value_type*
overload which kinda happened to work before this change (since it'd construct a ::path
and call that ctor), but this change breaks that. Should we undo this change here until an explicit const std::filesystem::path::value_type*
ctor exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That constructor already exists:
llvm-project/libcxx/include/fstream
Lines 1413 to 1415 in a417b9b
# ifdef _LIBCPP_HAS_OPEN_WITH_WCHAR | |
_LIBCPP_HIDE_FROM_ABI explicit basic_fstream(const wchar_t* __s, | |
ios_base::openmode __mode = ios_base::in | ios_base::out); |
It's definitely expected that you can't open an fstream with a wstring
, the constructor taking a filesystem::path
should not be used to convert "anything string-like, with any character type, in any encoding" into a file name with implicit allocations and character conversions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want that conversion, you can still do it explicitly:
std::fstream model_file(filesystem::path(ml_package_dir()
.Append(kMlPackageDataDir)
.Append(kMlPackageModelFileName)
.value()),
std::ios::out | std::ios::binary);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I suppose I need to call c_str()
on my wstring to get the wchar_t ctor. Thanks, let me try that; sorry for the noise.
(The point stands that this breaks existing code, though.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's indeed correct this may break existing code. Louis tested this at Apple, based on the results we applied the possibly breaking change. Is the breakage for you worse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it's just one file so far (). I got a bit confused because the wchar_t ctor is missing from the synopsis comment at the top. So all good.
*: But actually rolling this in is blocked on #86843 (comment) , so not 100% sure yet. But very likely that's all.
Implement LWG3430.