Skip to content
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

Conversation

Copy link

github-actions bot commented Jul 3, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@H-G-Hristov H-G-Hristov marked this pull request as ready for review July 5, 2024 11:40
@H-G-Hristov H-G-Hristov requested a review from a team as a code owner July 5, 2024 11:40
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 5, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jul 5, 2024

@llvm/pr-subscribers-libcxx

Author: Hristo Hristov (H-G-Hristov)

Changes

Implements as DR11: https://wg21.link/P2968R2

References:


Full diff: https://github.com/llvm/llvm-project/pull/97401.diff

7 Files Affected:

  • (modified) libcxx/docs/ReleaseNotes/19.rst (+1)
  • (modified) libcxx/include/CMakeLists.txt (+1)
  • (added) libcxx/include/__tuple/ignore.h (+35)
  • (modified) libcxx/include/module.modulemap (+1)
  • (modified) libcxx/include/tuple (+7-17)
  • (modified) libcxx/include/utility (+4)
  • (modified) libcxx/test/std/utilities/tuple/tuple.general/ignore.pass.cpp (+42-32)
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;
 }


struct __ignore_type {
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI constexpr const __ignore_type& operator=(const _Tp&) const noexcept {
Copy link
Contributor

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;
Copy link
Contributor

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).

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor Author

@H-G-Hristov H-G-Hristov Jul 7, 2024

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)?

Copy link
Contributor

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.

Copy link
Member

@mordante mordante left a 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.

@@ -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)
Copy link
Member

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.

Copy link
Contributor Author

@H-G-Hristov H-G-Hristov Jul 7, 2024

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

libcxx/include/utility Show resolved Hide resolved
}
};

inline constexpr __ignore_type ignore;
Copy link
Member

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?

Copy link
Contributor

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.

Copy link
Member

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?

Copy link
Contributor

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.

Copy link
Member

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

Copy link
Member

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.

@mordante mordante self-assigned this Jul 5, 2024
@H-G-Hristov
Copy link
Contributor Author

I updated the implementation in accordance with the comments by @mordante @frederick-vs-ja and @philnik777.


// UNSUPPORTED: c++03

// <tuple>
Copy link
Member

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>.

Copy link
Contributor Author

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 .

Copy link
Member

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.

Comment on lines 17 to 21
#include "test_macros.h"

int main(int, char**) {
{ [[maybe_unused]] TEST_CONSTEXPR_CXX14 auto& ignore_v = std::ignore; }
}
Copy link
Member

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.

Suggested change
#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; }

Comment on lines 29 to 32
# if _LIBCPP_STD_VER >= 17
inline
# endif
_LIBCPP_CONSTEXPR_SINCE_CXX14 __ignore_type ignore;
Copy link
Member

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?

@Zingam Zingam changed the title [libc++][tuple][utility] P2968R2 Make std::ignore a first-class object [libc++][tuple][utility] P2968R2: Make std::ignore a first-class object Jul 7, 2024
@Zingam Zingam requested a review from mordante July 7, 2024 15:48
@mordante
Copy link
Member

mordante commented Jul 8, 2024

I've just committed #97951 can you rebase your patch on main and update the status page for this patch.

@H-G-Hristov
Copy link
Contributor Author

I've just committed #97951 can you rebase your patch on main and update the status page for this patch.

Done! Thank you!

Copy link
Member

@mordante mordante left a 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.

Copy link
Member

@ldionne ldionne left a 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;
Copy link
Member

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.

@Zingam
Copy link
Contributor

Zingam commented Jul 10, 2024

Thank you!

@Zingam Zingam merged commit 31c9c41 into llvm:main Jul 10, 2024
58 checks passed
@H-G-Hristov H-G-Hristov deleted the hgh/libcxx/P2968R2-Make-std-ignore-a-first-class-object branch July 14, 2024 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants