[libcxx-commits] [libcxx] [libc++][ranges] Updated `[[nodiscard]]` implementation for `subrange` and `join_with_view` (PR #176936)
Hristo Hristov via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jan 21 23:16:31 PST 2026
https://github.com/H-G-Hristov updated https://github.com/llvm/llvm-project/pull/176936
>From ffbc40af303e65224575c050525307e3dd91aab3 Mon Sep 17 00:00:00 2001
From: Hristo Hristov <hghristov.rmm at gmail.com>
Date: Tue, 20 Jan 2026 16:35:05 +0200
Subject: [PATCH 1/3] [libc++][ranges] Updated `[[nodiscard]]` implementation
for `subrange` and `join_with_view`
Added or removed `[[nodiscard]]` according to the guidelines and updated the tests.
- https://libcxx.llvm.org/CodingGuidelines.html
Towards #172124
---
libcxx/include/__ranges/join_with_view.h | 5 +-
libcxx/include/__ranges/subrange.h | 10 +-
.../range.join.with/nodiscard.verify.cpp | 127 ++++++++----------
.../range.subrange/nodiscard.verify.cpp | 50 +++++++
4 files changed, 110 insertions(+), 82 deletions(-)
create mode 100644 libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
diff --git a/libcxx/include/__ranges/join_with_view.h b/libcxx/include/__ranges/join_with_view.h
index 8ed989a664687..3ce9011762e26 100644
--- a/libcxx/include/__ranges/join_with_view.h
+++ b/libcxx/include/__ranges/join_with_view.h
@@ -372,7 +372,7 @@ struct join_with_view<_View, _Pattern>::__iterator
return __tmp;
}
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y)
requires __ref_is_glvalue && forward_range<_Base> && equality_comparable<_InnerIter>
{
return __x.__outer_it_ == __y.__outer_it_ && __x.__inner_it_ == __y.__inner_it_;
@@ -420,8 +420,7 @@ struct join_with_view<_View, _Pattern>::__sentinel {
template <bool _OtherConst>
requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
- operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
return __get_outer_of(__x) == __y.__end_;
}
};
diff --git a/libcxx/include/__ranges/subrange.h b/libcxx/include/__ranges/subrange.h
index 87e1991a1f3b9..3d94b0a72b963 100644
--- a/libcxx/include/__ranges/subrange.h
+++ b/libcxx/include/__ranges/subrange.h
@@ -131,7 +131,7 @@ class subrange : public view_interface<subrange<_Iter, _Sent, _Kind>> {
return _Pair(__begin_, __end_);
}
- _LIBCPP_HIDE_FROM_ABI constexpr _Iter begin() const
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Iter begin() const
requires copyable<_Iter>
{
return __begin_;
@@ -143,11 +143,11 @@ class subrange : public view_interface<subrange<_Iter, _Sent, _Kind>> {
return std::move(__begin_);
}
- _LIBCPP_HIDE_FROM_ABI constexpr _Sent end() const { return __end_; }
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Sent end() const { return __end_; }
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool empty() const { return __begin_ == __end_; }
- _LIBCPP_HIDE_FROM_ABI constexpr make_unsigned_t<iter_difference_t<_Iter>> size() const
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr make_unsigned_t<iter_difference_t<_Iter>> size() const
requires(_Kind == subrange_kind::sized)
{
if constexpr (_StoreSize)
@@ -214,7 +214,7 @@ subrange(_Range&&, make_unsigned_t<range_difference_t<_Range>>)
template <size_t _Index, class _Iter, class _Sent, subrange_kind _Kind>
requires((_Index == 0 && copyable<_Iter>) || _Index == 1)
-_LIBCPP_HIDE_FROM_ABI constexpr auto get(const subrange<_Iter, _Sent, _Kind>& __subrange) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto get(const subrange<_Iter, _Sent, _Kind>& __subrange) {
if constexpr (_Index == 0)
return __subrange.begin();
else
@@ -223,7 +223,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto get(const subrange<_Iter, _Sent, _Kind>& __
template <size_t _Index, class _Iter, class _Sent, subrange_kind _Kind>
requires(_Index < 2)
-_LIBCPP_HIDE_FROM_ABI constexpr auto get(subrange<_Iter, _Sent, _Kind>&& __subrange) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto get(subrange<_Iter, _Sent, _Kind>&& __subrange) {
if constexpr (_Index == 0)
return __subrange.begin();
else
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
index dc2a9f5074ae4..df21cf15182df 100644
--- a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
@@ -10,7 +10,6 @@
// Test that functions are marked [[nodiscard]].
-#include <array>
#include <ranges>
#include <utility>
@@ -23,77 +22,57 @@ void test() {
std::ranges::join_with_view view(range, pattern);
- // clang-format off
- view.base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::as_const(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::move(std::as_const(view)).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::move(view).base(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-
- // clang-format off
- view.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::as_const(view).begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-
- // clang-format off
- view.end(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::as_const(view).end(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-}
-
-void test_iterator() {
- char range[3][2] = {{'x', 'x'}, {'y', 'y'}, {'z', 'z'}};
- char pattern[2] = {',', ' '};
-
- std::ranges::join_with_view view(range, pattern);
-
- // clang-format off
- *view.begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- *std::as_const(view).begin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-
- // clang-format off
- (view.begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (std::as_const(view).begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (view.begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (std::as_const(view).begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-
- // clang-format off
- iter_move(view.begin()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- iter_move(std::as_const(view).begin()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-}
-
-void test_sentinel() {
- std::array<test_range<cpp20_input_iterator>, 0> range;
- std::array<int, 0> pattern;
-
- std::ranges::join_with_view view(range, pattern);
- static_assert(!std::ranges::common_range<decltype(view)>);
-
- // clang-format off
- (view.begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (std::as_const(view).begin() == view.end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (view.begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- (std::as_const(view).begin() == std::as_const(view).end()); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
-}
-
-void test_overview() {
- int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
- int pattern_base[2] = {-1, -1};
- auto pattern = std::views::all(pattern_base);
-
- // clang-format off
- std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::join_with(range, pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- range | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::reverse | std::views::join_with(pattern); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
-
- std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::join_with(range, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- range | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::reverse | std::views::join_with(0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
- // clang-format on
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ view.base();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).base();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(std::as_const(view)).base();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(view).base();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ view.begin();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).begin();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ view.end();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(view).end();
+
+ // }
+
+ // void test_iterator() {
+ // char range[3][2] = {{'x', 'x'}, {'y', 'y'}, {'z', 'z'}};
+ // char pattern[2] = {',', ' '};
+
+ // std::ranges::join_with_view view(range, pattern);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ *view.begin();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ *std::as_const(view).begin();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ iter_move(view.begin());
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ iter_move(std::as_const(view).begin());
+
+ // }
+
+ // void test_overview() {
+ // int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+ // int pattern_base[2] = {-1, -1};
+ // auto pattern = std::views::all(pattern_base);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(pattern);
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(range, pattern);
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(0);
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::views::join_with(range, 0);
}
diff --git a/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
new file mode 100644
index 0000000000000..0f457ce019bea
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++20
+
+// Test that functions are marked [[nodiscard]].
+
+#include <ranges>
+#include <utility>
+#include <vector>
+
+#include "test_range.h"
+
+void test() {
+ std::vector<int> range;
+ std::ranges::subrange subrange{range.begin(), range.end()};
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).begin();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ subrange.begin();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).end();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).empty();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).size();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).next();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(subrange).next();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::as_const(subrange).prev();
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::move(subrange).prev();
+
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::get<0>(std::as_const(subrange));
+ // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::get<0>(std::move(subrange));
+}
>From 04c5cc91f55e117232a3a3115f9ac7d167d3c423 Mon Sep 17 00:00:00 2001
From: Hristo Hristov <hghristov.rmm at gmail.com>
Date: Tue, 20 Jan 2026 17:43:18 +0200
Subject: [PATCH 2/3] MoveOnly
---
.../range.join.with/nodiscard.verify.cpp | 36 +++++--------------
.../range.subrange/nodiscard.verify.cpp | 29 +++++++++++++--
2 files changed, 35 insertions(+), 30 deletions(-)
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
index df21cf15182df..f94992c443265 100644
--- a/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.join.with/nodiscard.verify.cpp
@@ -22,13 +22,11 @@ void test() {
std::ranges::join_with_view view(range, pattern);
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- view.base();
+ // [range.join.with.view]
+
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::as_const(view).base();
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::move(std::as_const(view)).base();
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::move(view).base();
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
@@ -41,38 +39,20 @@ void test() {
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::as_const(view).end();
- // }
+ // [range.join.with.iterator]
- // void test_iterator() {
- // char range[3][2] = {{'x', 'x'}, {'y', 'y'}, {'z', 'z'}};
- // char pattern[2] = {',', ' '};
+ auto cIt = std::as_const(view).begin();
- // std::ranges::join_with_view view(range, pattern);
-
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- *view.begin();
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- *std::as_const(view).begin();
+ *cIt;
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- iter_move(view.begin());
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- iter_move(std::as_const(view).begin());
+ iter_move(cIt);
- // }
+ // [range.join.with.overview]
- // void test_overview() {
- // int range[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
- // int pattern_base[2] = {-1, -1};
- // auto pattern = std::views::all(pattern_base);
-
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::join_with(pattern);
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::views::join_with(range, pattern);
-
- // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::join_with(0);
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- std::views::join_with(range, 0);
+ std::views::join_with(pattern);
}
diff --git a/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
index 0f457ce019bea..9ef94873b26eb 100644
--- a/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/ranges/range.utility/range.subrange/nodiscard.verify.cpp
@@ -14,16 +14,41 @@
#include <utility>
#include <vector>
-#include "test_range.h"
+struct MoveOnlyIterator {
+ using iterator_concept = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = int;
+
+ MoveOnlyIterator() = default;
+
+ MoveOnlyIterator(MoveOnlyIterator&&) = default;
+ MoveOnlyIterator& operator=(MoveOnlyIterator&&) = default;
+
+ MoveOnlyIterator(const MoveOnlyIterator&) = delete;
+ MoveOnlyIterator& operator=(const MoveOnlyIterator&) = delete;
+
+ int operator*() const;
+
+ MoveOnlyIterator& operator++();
+
+ void operator++(int);
+
+ friend bool operator==(const MoveOnlyIterator&, std::default_sentinel_t);
+};
+static_assert(std::input_iterator<MoveOnlyIterator>);
+static_assert(!std::copyable<MoveOnlyIterator>);
void test() {
std::vector<int> range;
std::ranges::subrange subrange{range.begin(), range.end()};
+ MoveOnlyIterator it;
+ auto moveOnlySubrange = std::ranges::subrange(std::move(it), std::default_sentinel);
+
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::as_const(subrange).begin();
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
- subrange.begin();
+ std::move(moveOnlySubrange).begin();
// expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::as_const(subrange).end();
>From 3b1e6b4b23ecd8e0dd24a8cd2a35e81737659664 Mon Sep 17 00:00:00 2001
From: Hristo Hristov <hghristov.rmm at gmail.com>
Date: Wed, 21 Jan 2026 10:43:32 +0200
Subject: [PATCH 3/3] Revert an unintentional change
---
libcxx/include/__ranges/subrange.h | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/libcxx/include/__ranges/subrange.h b/libcxx/include/__ranges/subrange.h
index 3d94b0a72b963..62b1c96149799 100644
--- a/libcxx/include/__ranges/subrange.h
+++ b/libcxx/include/__ranges/subrange.h
@@ -201,8 +201,7 @@ template <input_or_output_iterator _Iter, sentinel_for<_Iter> _Sent>
subrange(_Iter, _Sent, make_unsigned_t<iter_difference_t<_Iter>>) -> subrange<_Iter, _Sent, subrange_kind::sized>;
template <borrowed_range _Range>
-subrange(_Range&&)
- -> subrange<iterator_t<_Range>,
+subrange(_Range&&) -> subrange<iterator_t<_Range>,
sentinel_t<_Range>,
(sized_range<_Range> || sized_sentinel_for<sentinel_t<_Range>, iterator_t<_Range>>)
? subrange_kind::sized
More information about the libcxx-commits
mailing list