Details
- Reviewers
• Quuxplusone Mordante - Group Reviewers
Restricted Project - Commits
- rGbec50db2edf6: [libc++] Implement P1072R10 (std::basic_string::resize_and_overwrite)
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Thanks for working on this!
Please mark the feature complete in libcxx/docs/Status/Cxx2bPapers.csv.
Please update libcxx/utils/generate_feature_test_macro_components.py with the new feature-test macro __cpp_lib_string_resize_and_overwrite; its values should be 202110L and run this script to update the appropriate tests.
I didn't have enough time to do a thorough review; I'll have a closer look after all issues have been addressed.
libcxx/include/string | ||
---|---|---|
160–163 | Please update the synopsis. template<class Operation> constexpr void resize_and_overwrite(size_type n, Operation op); // since C++23 | |
985 | This looks wrong. "Implementations should avoid unnecessary copies and allocations" This may allocate more than required. The paper 3. Implementation refers to __resize_default_init as example. | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
14 | Please use the entire signature of the function. | |
21 | All tests are now done on an empty string. Please also test with non-empty strings. | |
65 | It would be good to test with all string types. libcxx/test/support/make_string.h has a helper to create the same string for all supported string types. Then the test function can be templated on the string type. | |
77 | Please use int main(int, char**). | |
80 | Please return 0; Some of our supported platforms require this even for main. |
libcxx/include/string | ||
---|---|---|
993 | Line-break after constexpr, please. | |
994 | http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1072r10.html doesn't mention any Constraints, only a Mandates that std::move(__o)(declval<_CharT*&>(), __n) should have an integer-like type. I don't think "integer-like-ness" is expressible in C++, but prove me wrong. :) ...Hm, I guess [p1072r10] has a minor wording bug, because obviously they intended to mandate the type of std::move(op)(std::move(p), std::move(n)), not std::move(op)(p, n). I think we should "do what they mean, not what they say." | |
998 | Forgot the std::move here. Please add a test case that catches this (e.g. use an _Op with a &&-ref-qualified operator(), and/or one where operator()() && and operator()() & do different things). | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
77 | I question the usefulness of all of the "nonsense" strings. I understand "" and "Banane" (i.e. "short") and "long long string so no SSO", but the other five seem calculated only to make the z/OS folks unhappy. ;) If they're redundant, please eliminate them. |
libcxx/include/string | ||
---|---|---|
993 | "I wouldn't complain if X" means "I'm not asking for X, but I'd be pleased if X were to happen anyway." Okay, usually it means I'm asking for X. ;) | |
994 | The question is whether it is permissible for libc++ to reject std::string s; s.resize_and_overwrite(100, [](char *p, int& n) { return 0; }); or, vice versa, whether it is permissible for us to accept std::string s; s.resize_and_overwrite(100, [](char *p, int&& n) { return 0; }); That is, it's not so much about the machine code generated as it is about the value-categories of things. | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
23–39 | For the ref-qualification/value-category issue, this simple non-template test will suffice; you can get rid of move_only_functor. void test_value_categories() { std::string s; s.resize_and_overwrite(10, [](char*&, size_t&) { return 0; }); struct RefQual { int operator()(char *, size_t) && { return 0; } }; s.resize_and_overwrite(10, RefQual()); } (Here I'm following libstdc++ in assuming that p, n are passed as lvalues, even though I said above that they really ought to be passed as rvalues. If we hear that rvalues were intended, then just change & to && above.) | |
90–95 | Throughout: I see your reason for using the names begin, size for Op's parameters, but I think it will be clearer to use p, n the same way the standard does. Also, speaking of s.begin() and s.size(), it looks like you're assuming things about their values without asserting those things. Could we assert them instead? constexpr void test_undersized(S src, S orig) { S dst = orig; size_t new_size = 1.5 * src.size(); auto func = [&](auto *p, size_t n) { assert(n == new_size); assert(dst.size() == new_size); p = std::copy_n(src.begin(), src.size(), p); std::copy_n(src.begin(), src.size() / 2, p); return n; }; dst.resize_and_overwrite(new_size, func); assert(dst == src + src.substr(0, src.size() / 2); } But, writing it out this way, I just don't see the point of this test. What is orig doing there (your former s3)? Why does your func overwrite not just the new space but the original contents of the string too? What is the significance of the division in size() / 2 as opposed to using a constant like 42? template<class S> constexpr void test_appending(int k, int N) { assert(N > k); auto s = S(k, 'a'); s.resize_and_overwrite(N, [&](char *p, int n) { assert(n == N); LIBCPP_ASSERT(s.size() == n); LIBCPP_ASSERT(s.begin() == p); assert(std::all_of(p, p+k, [](auto ch) { return ch == 'a'; })); LIBCPP_ASSERT(p[k] == '\0'); std::fill(p+k, p+n, 'b'); p[n] = 'c'; // will be overwritten return n; }); auto expected = S(k, 'a') + S(N-k, 'b'); assert(s == expected); } template<class S> constexpr void test_truncating(int o, int N) { assert(N < o); auto s = S(o, 'a'); s.resize_and_overwrite(N, [&](char *p, int n) { assert(n == N); LIBCPP_ASSERT(s.size() == n); LIBCPP_ASSERT(s.begin() == p); assert(std::all_of(p, p+n, [](auto ch) { return ch == 'a'; })); LIBCPP_ASSERT(p[n] == 'a'); p[n-1] = 'b'; p[n] = 'c'; // will be overwritten return n; }); auto expected = S(N-1, 'a') + 'b'; assert(s == expected); } However, this is still missing any coverage for what happens when op(p, n) < n. We should have coverage for that. | |
108–113 | Throughout: IIUC, s1 means dest and s2 means src. (On line 87, s means dest and s3 means src. And so on.) Please consistent-ize the parameter names. | |
124 | template<class CharT> — it's not a String. | |
133–135 | (Moot) These lines are too long. |
LGTM at this point, mod minor comments.
libcxx/include/string | ||
---|---|---|
339–340 | Please move this up to between resize and capacity, to match https://eel.is/c++draft/basic.string | |
2668 | Here and line 3495, since we're changing it anyway, I'd prefer to see _LIBCPP_CONSTEXPR_AFTER_CXX17 void We shouldn't need the inline keyword at all, because this is a template; and constexpr void reads better than void constexpr. | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
24 | Neat. Perhaps resizeSize should be named newCapacity? (The ultimate size of the final string will be N.) | |
62 | I think you're missing an interesting case: something like test_appending<S>(10, 30, 15), where the original string fits in SSO, then we need to allocate for the lambda call, and then the final string would again fit in SSO (but IIUC we do not actually put it back into SSO — we just leave it with the extra allocated capacity, right? This is fine). |
In general this looks good! I've some minor nit. It also seems you missed some of my "bookkeeping" remarks. I added these as comments.
libcxx/include/string | ||
---|---|---|
339–340 | Basically where my original request for updating the synopsis is ;-) | |
340 | In my original reply I mentioned some not yet done things. For clarity I add them here to it's easier to tick the box once done. Please mark the feature complete in libcxx/docs/Status/Cxx2bPapers.csv. Please update libcxx/utils/generate_feature_test_macro_components.py with the new feature-test macro __cpp_lib_string_resize_and_overwrite; its values should be 202110L and run this script to update the appropriate tests. | |
996 | Is there a reason to use variable instead of calling data() in __erase_to_end. | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
24 | Also please use snake_case. | |
37 | Please don't use auto. | |
38 | Can you test whether s.c_str() contains a proper NUL-terminated string. The same for the truncating test. | |
87 | Next time it would be better to make a helper function that calls all 5 variants, just to keep main simpler. |
libcxx/include/string | ||
---|---|---|
996 | Sadly yes, OP's parameters are overspecified as lvalues, not prvalues. I've just requested an LWG issue number for this. | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
38 | Ah, +1; I'd been implicitly assuming that s == expected would also check the null byte, but I guess it doesn't necessarily have to. const S expected = S(k, 'a') + S(N - k, 'b'); assert(s == expected); assert(s.c_str()[N] == '\0'); | |
87 | I forget if I specifically requested the current status quo, but I very well might have — personally I like <10 LOC of code repetition in main better than a second layer of test functions. :) |
libcxx/include/string | ||
---|---|---|
996 | In that case once you have a number, can you provide it here. Then we can add a comment why this is required. (And clean it up once the issue is resolved.) | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
87 | I prefer the extra layer, especially due to the duplication for static_assert. |
libcxx/include/string | ||
---|---|---|
996 | @mzeren-vmw @ckennelly (paper authors), thoughts? Shall we even just go ahead and do the sane thing here, on the assumption that LWG will catch up to us? :) |
- Remove _LIBCPP_CONSTEXPR_AFTER_CXX17
- Disable constexpr test
@Quuxplusone, @Mordante do you want to take another look before I land it?
libcxx/include/string | ||
---|---|---|
996 | The LWG issue for this is https://timsong-cpp.github.io/lwg-issues/3645 | |
libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp | ||
32 | Nit: remove const, or add &, depending on your intent |
I don't mind landing it now, but let's make sure we show the correct state. In the followup patch you can then update the state to fully implemented.
libcxx/docs/Status/Cxx2bPapers.csv | ||
---|---|---|
29 | Please add a note the constexpr part isn't done yet. | |
libcxx/utils/generate_feature_test_macro_components.py | ||
630 | Likewise show the correct state. |
@Mordante I'd argue that it is fully implemented. The function signature is complete. It only doesn't work, because the rest of string isn't constexpr yet. And P1679R3 is also marked complete, including feature test macro.
Please add a note the constexpr part isn't done yet.
The LLVM 14 branching will happen later this month, so it's not certain the complete version will be in LLVM 14. So let's make the state correct.