-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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++][tuple][utility] P2968R2: Make std::ignore
a first-class object
#97401
[libc++][tuple][utility] P2968R2: Make std::ignore
a first-class object
#97401
Conversation
Implements as DR11: https://wg21.link/P2968R2 References: - https://eel.is/c++draft/tuple.general - https://eel.is/c++draft/tuple.syn - https://eel.is/c++draft/tuple.creation - https://github.com/cplusplus/draft/milestone/31 - cplusplus/papers#1640 - https://cplusplus.github.io/LWG/issue2933 - https://cplusplus.github.io/LWG/issue3978
✅ With the latest revision this PR passed the C/C++ code formatter. |
…' of https://github.com/H-G-Hristov/llvm-project into hgh/libcxx/P2968R2-Make-std-ignore-a-first-class-object
@llvm/pr-subscribers-libcxx Author: Hristo Hristov (H-G-Hristov) ChangesImplements as DR11: https://wg21.link/P2968R2 References:
Full diff: https://github.com/llvm/llvm-project/pull/97401.diff 7 Files Affected:
diff --git a/libcxx/docs/ReleaseNotes/19.rst b/libcxx/docs/ReleaseNotes/19.rst
index d30021b7eb2347..5c3737d79876e4 100644
--- a/libcxx/docs/ReleaseNotes/19.rst
+++ b/libcxx/docs/ReleaseNotes/19.rst
@@ -46,6 +46,7 @@ Implemented Papers
- P2872R3 - Remove ``wstring_convert`` From C++26
- P3142R0 - Printing Blank Lines with ``println`` (as DR against C++23)
- P2944R3 - Comparisons for ``reference_wrapper`` (comparison operators for ``reference_wrapper`` only)
+- P2968R2 - Make ``std::ignore`` a first-class object (as DR against C++11)
- P2302R4 - ``std::ranges::contains``
- P1659R3 - ``std::ranges::starts_with`` and ``std::ranges::ends_with``
- P3029R1 - Better ``mdspan``'s CTAD
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 8d0ffd6ed725bd..07dd25604a9c76 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -710,6 +710,7 @@ set(files
__thread/timed_backoff_policy.h
__tree
__tuple/find_index.h
+ __tuple/ignore.h
__tuple/make_tuple_types.h
__tuple/sfinae_helpers.h
__tuple/tuple_element.h
diff --git a/libcxx/include/__tuple/ignore.h b/libcxx/include/__tuple/ignore.h
new file mode 100644
index 00000000000000..a00fc7e7d5b85e
--- /dev/null
+++ b/libcxx/include/__tuple/ignore.h
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___TUPLE_IGNORE_H
+#define _LIBCPP___TUPLE_IGNORE_H
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+#ifndef _LIBCPP_CXX03_LANG
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+struct __ignore_type {
+ template <class _Tp>
+ _LIBCPP_HIDE_FROM_ABI constexpr const __ignore_type& operator=(const _Tp&) const noexcept {
+ return *this;
+ }
+};
+
+inline constexpr __ignore_type ignore;
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_CXX03_LANG
+
+#endif // _LIBCPP___TUPLE_IGNORE_H
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 9ffccf66ff0948..4ad506781c489a 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1840,6 +1840,7 @@ module std_private_thread_thread [system] {
module std_private_thread_timed_backoff_policy [system] { header "__thread/timed_backoff_policy.h" }
module std_private_tuple_find_index [system] { header "__tuple/find_index.h" }
+module std_private_tuple_ignore [system] { header "__tuple/ignore.h" }
module std_private_tuple_make_tuple_types [system] { header "__tuple/make_tuple_types.h" }
module std_private_tuple_tuple_like_no_subrange [system] {
header "__tuple/tuple_like_no_subrange.h"
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 26652ffe81e9f0..d2f6e19ede68df 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -132,7 +132,12 @@ tuple(allocator_arg_t, Alloc, pair<T1, T2>) -> tuple<T1, T2>; // since C++
template <class Alloc, class ...T>
tuple(allocator_arg_t, Alloc, tuple<T...>) -> tuple<T...>; // since C++17
-inline constexpr unspecified ignore;
++struct ignore-type { // exposition only
++ constexpr const ignore-type&
++ operator=(const auto &) const noexcept
++ { return *this; }
++};
+inline constexpr ignore-type ignore;
template <class... T> tuple<V...> make_tuple(T&&...); // constexpr in C++14
template <class... T> tuple<ATypes...> forward_as_tuple(T&&...) noexcept; // constexpr in C++14
@@ -215,6 +220,7 @@ template <class... Types>
#include <__memory/allocator_arg_t.h>
#include <__memory/uses_allocator.h>
#include <__tuple/find_index.h>
+#include <__tuple/ignore.h>
#include <__tuple/make_tuple_types.h>
#include <__tuple/sfinae_helpers.h>
#include <__tuple/tuple_element.h>
@@ -1112,22 +1118,6 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 tuple<_Tp&...> tie(_T
return tuple<_Tp&...>(__t...);
}
-template <class _Up>
-struct __ignore_t {
- template <class _Tp>
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 const __ignore_t& operator=(_Tp&&) const {
- return *this;
- }
-};
-
-# if _LIBCPP_STD_VER >= 17
-inline constexpr __ignore_t<unsigned char> ignore = __ignore_t<unsigned char>();
-# else
-namespace {
-constexpr __ignore_t<unsigned char> ignore = __ignore_t<unsigned char>();
-} // namespace
-# endif
-
template <class... _Tp>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 tuple<typename __unwrap_ref_decay<_Tp>::type...>
make_tuple(_Tp&&... __t) {
diff --git a/libcxx/include/utility b/libcxx/include/utility
index 90713da621c5da..f2f0052df27552 100644
--- a/libcxx/include/utility
+++ b/libcxx/include/utility
@@ -274,6 +274,10 @@ template <class T>
#include <compare>
#include <initializer_list>
+// [tuple.creation]
+
+#include <__tuple/ignore.h>
+
// [tuple.helper]
#include <__tuple/tuple_element.h>
#include <__tuple/tuple_size.h>
diff --git a/libcxx/test/std/utilities/tuple/tuple.general/ignore.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.general/ignore.pass.cpp
index 769c55e10fc438..49f3249a4e413a 100644
--- a/libcxx/test/std/utilities/tuple/tuple.general/ignore.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.general/ignore.pass.cpp
@@ -6,51 +6,61 @@
//
//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03
+
// <tuple>
-// constexpr unspecified ignore;
-
-// UNSUPPORTED: c++03
+// inline constexpr ignore-type ignore;
#include <cassert>
+#include <cstdint>
#include <tuple>
#include <type_traits>
#include "test_macros.h"
-constexpr bool test_ignore_constexpr()
-{
-#if TEST_STD_VER > 11
- { // Test that std::ignore provides constexpr converting assignment.
- auto& res = (std::ignore = 42);
- assert(&res == &std::ignore);
- }
- { // Test that std::ignore provides constexpr copy/move constructors
- auto copy = std::ignore;
- auto moved = std::move(copy);
- ((void)moved);
- }
- { // Test that std::ignore provides constexpr copy/move assignment
- auto copy = std::ignore;
- copy = std::ignore;
- auto moved = std::ignore;
- moved = std::move(copy);
- }
+static_assert(std::is_trivial<decltype(std::ignore)>::value, "");
+
+#if TEST_STD_VER >= 17
+[[nodiscard]] constexpr int test_nodiscard() { return 8294; }
#endif
- return true;
+
+constexpr bool test() {
+#if TEST_STD_VER >= 17
+ { std::ignore = test_nodiscard(); }
+#endif
+
+ return true;
}
int main(int, char**) {
- {
- constexpr auto& ignore_v = std::ignore;
- ((void)ignore_v);
- }
- {
- static_assert(test_ignore_constexpr(), "");
- }
- {
- LIBCPP_STATIC_ASSERT(std::is_trivial<decltype(std::ignore)>::value, "");
- }
+ test();
+ static_assert(test(), "");
+
+ { [[maybe_unused]] constexpr auto& ignore_v = std::ignore; }
+ { // Test that std::ignore provides constexpr converting assignment.
+ constexpr auto& res = (std::ignore = 42);
+ static_assert(noexcept(res = (std::ignore = 42)), "Must be noexcept");
+ assert(&res == &std::ignore);
+ }
+ { // Test bit-field binding.
+ struct S {
+ unsigned int bf : 3;
+ };
+ constexpr S s{0b010};
+ constexpr auto& res = (std::ignore = s.bf);
+ assert(&res == &std::ignore);
+ }
+ { // Test that std::ignore provides constexpr copy/move constructors
+ constexpr auto copy = std::ignore;
+ [[maybe_unused]] constexpr auto moved = std::move(copy);
+ }
+ { // Test that std::ignore provides constexpr copy/move assignment
+ constexpr auto copy = std::ignore;
+ copy = std::ignore;
+ constexpr auto moved = std::ignore;
+ moved = std::move(copy);
+ }
return 0;
}
|
libcxx/include/__tuple/ignore.h
Outdated
|
||
struct __ignore_type { | ||
template <class _Tp> | ||
_LIBCPP_HIDE_FROM_ABI constexpr const __ignore_type& operator=(const _Tp&) const noexcept { |
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.
Previously this was _LIBCPP_CONSTEXPR_SINCE_CXX14
, which was synchonized with constexpr
additions to tuple
(via N3471 in C++14). Not sure further (?) backport of constexpr
is desired...
} | ||
}; | ||
|
||
inline constexpr __ignore_type ignore; |
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 is backporting a change in C++17 (P0607R0)... I think in C++11/14 std::ignore
was supported to have internal linkage (and thus denoted different objects in different TUs).
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 is backporting a change in C++17 (P0607R0)... I think in C++11/14
std::ignore
was supported to have internal linkage (and thus denoted different objects in different TUs).
What's the benefit of doing this?
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.
What's the benefit of doing this?
This is what libc++ is doing currently (since 8643bdd). I guess it would be better to backport inline
additions in a concentrated PR if wanted.
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.
@frederick-vs-ja Thanks for the review. Isn't the current implementation in STL in line with what's I implemented here (also regarding your comment above)?
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.
MSVC STL is currently marking these non-templated constexpr
variables _INLINE_VAR
, which expands to inline
only since C++17. _INLINE_VAR
expands to nothing in C++14 mode, which makes constexpr
variables non-inline and have internal linkage. I think libc++ is effectively doing the same.
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 working on this. I've some comments.
libcxx/docs/ReleaseNotes/19.rst
Outdated
@@ -46,6 +46,7 @@ Implemented Papers | |||
- P2872R3 - Remove ``wstring_convert`` From C++26 | |||
- P3142R0 - Printing Blank Lines with ``println`` (as DR against C++23) | |||
- P2944R3 - Comparisons for ``reference_wrapper`` (comparison operators for ``reference_wrapper`` only) | |||
- P2968R2 - Make ``std::ignore`` a first-class object (as DR against C++11) |
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 was not voted as DR in the plenary. Why have you implemented it as a DR?
I'm not against changing operator=(auto&&) const
to operator=(const auto &) const
for all language versions, but I feel that should be mentioned separately in the release notes.
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.
@mordante Thank you for the review.
Regarding the implementation: I am not sure what makes a paper a DR. https://en.cppreference.com/w/cpp/compiler_support/26 seems to have listed P2968R2 as DR11. I assumed that DR are listed here: cplusplus/draft#7078 but I don't have access to the link.
Why wasn't this voted as DR? To me it would make sense but I can't really tell. Should I restore the original implementation and tests for the pre-C++26 versions? Or keep the current implementation and update release notes as you suggest:
I'm not against changing operator=(auto&&) const to operator=(const auto &) const for all language versions, but I feel that should be mentioned separately in the release notes.
Could you please clarify?
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 explaining why you made this a DR.
Basically something becomes a DR when the committee votes it becomes a DR. The voting of this paper it does not mention a DR. (This information is currently only available for Committee members. For the previous meeting the information is public https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/n4980.pdf has the vote "Accept as a Defect Report and apply the changes in P3107R5 (Permit an efficient implementation of std::print) to the C++ working paper.")
So I would not make this paper a DR but keep the new class unconditionally in all language versions. However we should not use constexpr
, but keep the original macro.
The paper does mention LWG issues, but the paper does not propose itself as a Defect Report.
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.
@mordante Thanks. I think I just addressed these comments. Let's see if the CI will get green first.
} | ||
}; | ||
|
||
inline constexpr __ignore_type ignore; |
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 change seems like an ABI break, can you add _LIBCPP_HIDE_FROM_ABI
?
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'm not sure what ABI break you see, but this definitely shouldn't be makred _LIBCPP_HIDE_FROM_ABI
, since that would be non-conforming. You would get different addresses in different dylibs.
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.
The type of the ignore
changes and that type is not identical. Why would adding _LIBCPP_HIDE_FROM_ABI
be non-conforming?
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.
Sure it's technically an ODR violation, but that's benign AFAICT. We don't change the layout in any way. As I already said, it's non-conforming because the object would have different addresses in different dylibs.
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.
@ldionne are you happy with this change without using _LIBCPP_HIDE_FROM_ABI
.
Basically the change is a a different type and the signature operator=(_Tp&&) const
changed to operator=(const _Tp&) const noexcept
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.
The current way it is defined seems correct to me. We do need to ensure that ignore
has the same address across all TUs since the Standard says inline constexpr
explicitly, so we can't ABI-tag this.
I do agree this is technically an ABI break due to the change from __ignore_t<unsigned char>
to __ignore_type
, however I really really don't think it matters in practice. I don't even feel it is worth release-noting this ABI change since I think essentially nobody would ever notice.
I updated the implementation in accordance with the comments by @mordante @frederick-vs-ja and @philnik777. |
|
||
// UNSUPPORTED: c++03 | ||
|
||
// <tuple> |
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.
Can you add a comment along the lines of
std::ignore should be provided by the headers <tuple> and <utility>.
This tests validates its presence in <tuple>.
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.
The paper also "recommends" to mention the same in <tuple>
. Do you have any recommendations what and where to write?
YES, make ignore available through , and not repeat the specification, but follow the example of ranges accessor functions by saying in [tuple] somewhere that ignore is available also from header .
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'm not entirely sure what you mean with "The paper also "recommends" to mention the same in <tuple>
"
The wording section contains
In [tuple.general](https://eel.is/c++draft/tuple.general) append the following paragraph
<ins>In addition to being available via inclusion of the <tuple> header, ignore ([tuple.syn]) is available when <utility> ([utility]) is included.</ins>
This is not a recommendation but a requirement.
Can you explain what your question is? Feel free to do it on Discord if you prefer that.
#include "test_macros.h" | ||
|
||
int main(int, char**) { | ||
{ [[maybe_unused]] TEST_CONSTEXPR_CXX14 auto& ignore_v = std::ignore; } | ||
} |
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.
Since it's a compile-time test we don't need main.
I like to avoid adding other headers; that way we're sure no other Standard headers are included. That way we can verify provides std::ignore
.
#include "test_macros.h" | |
int main(int, char**) { | |
{ [[maybe_unused]] TEST_CONSTEXPR_CXX14 auto& ignore_v = std::ignore; } | |
} | |
[[maybe_unused]] auto& ignore_v = std::ignore; } |
libcxx/include/__tuple/ignore.h
Outdated
# if _LIBCPP_STD_VER >= 17 | ||
inline | ||
# endif | ||
_LIBCPP_CONSTEXPR_SINCE_CXX14 __ignore_type ignore; |
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.
The way the #if
was done originally was easier to understand. Can you use that style here instead?
…' of https://github.com/H-G-Hristov/llvm-project into hgh/libcxx/P2968R2-Make-std-ignore-a-first-class-object
std::ignore
a first-class objectstd::ignore
a first-class object
I've just committed #97951 can you rebase your patch on main and update the status page for this patch. |
Done! Thank you! |
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 this basically LGTM. However I'd like to hear @ldionne's input on _LIBCPP_HIDE_FROM_ABI
before you land this patch.
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 LGTM!
} | ||
}; | ||
|
||
inline constexpr __ignore_type ignore; |
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.
The current way it is defined seems correct to me. We do need to ensure that ignore
has the same address across all TUs since the Standard says inline constexpr
explicitly, so we can't ABI-tag this.
I do agree this is technically an ABI break due to the change from __ignore_t<unsigned char>
to __ignore_type
, however I really really don't think it matters in practice. I don't even feel it is worth release-noting this ABI change since I think essentially nobody would ever notice.
Thank you! |
…ject (llvm#97401) Implements: https://wg21.link/P2968R2 References: - https://eel.is/c++draft/tuple.general - https://eel.is/c++draft/tuple.syn - https://eel.is/c++draft/tuple.creation - https://github.com/cplusplus/draft/milestone/31 - cplusplus/draft#7109 - cplusplus/papers#1640 - https://cplusplus.github.io/LWG/issue2933 - https://cplusplus.github.io/LWG/issue3978 --------- Co-authored-by: Hristo Hristov <[email protected]>
Implements: https://wg21.link/P2968R2
References: