diff --git a/stl/debugger/STL.natvis b/stl/debugger/STL.natvis
index 80fe4fb99..b9f0787bf 100644
--- a/stl/debugger/STL.natvis
+++ b/stl/debugger/STL.natvis
@@ -619,6 +619,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+ bind_back({_Mypair}, {_Mypair._Myval2,view(noparens)})
+
+ - _Mypair
+ - _Mypair._Myval2
+
+
+
diff --git a/stl/inc/functional b/stl/inc/functional
index fc68beb46..1ecd7fdb4 100644
--- a/stl/inc/functional
+++ b/stl/inc/functional
@@ -2130,6 +2130,83 @@ _NODISCARD constexpr auto bind_front(_Fx&& _Func, _Types&&... _Args) {
}
#endif // _HAS_CXX20
+#if _HAS_CXX23
+template
+constexpr auto _Call_back_binder(index_sequence<_Ix...>, _Cv_FD&& _Obj, _Cv_tuple_TiD&& _Tpl,
+ _Unbound&&... _Unbargs) noexcept(noexcept(_STD invoke(_STD forward<_Cv_FD>(_Obj),
+ _STD forward<_Unbound>(_Unbargs)..., _STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))...)))
+ -> decltype(_STD invoke(_STD forward<_Cv_FD>(_Obj), _STD forward<_Unbound>(_Unbargs)...,
+ _STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))...)) {
+ return _STD invoke(_STD forward<_Cv_FD>(_Obj), _STD forward<_Unbound>(_Unbargs)...,
+ _STD get<_Ix>(_STD forward<_Cv_tuple_TiD>(_Tpl))...);
+}
+
+template
+class _Back_binder { // wrap bound callable object and arguments
+private:
+ using _Seq = index_sequence_for<_Types...>;
+
+ _Compressed_pair<_Fx, tuple<_Types...>> _Mypair;
+
+ _STL_INTERNAL_STATIC_ASSERT(is_same_v<_Fx, decay_t<_Fx>>);
+ _STL_INTERNAL_STATIC_ASSERT((is_same_v<_Types, decay_t<_Types>> && ...));
+
+public:
+ template , _Back_binder>, int> = 0>
+ constexpr explicit _Back_binder(_FxInit&& _Func, _TypesInit&&... _Args)
+ : _Mypair(_One_then_variadic_args_t{}, _STD forward<_FxInit>(_Func), _STD forward<_TypesInit>(_Args)...) {}
+
+ template
+ constexpr auto operator()(_Unbound&&... _Unbargs) & noexcept(
+ noexcept(_Call_back_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)))
+ -> decltype(_Call_back_binder(
+ _Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)) {
+ return _Call_back_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...);
+ }
+
+ template
+ constexpr auto operator()(_Unbound&&... _Unbargs) const& noexcept(
+ noexcept(_Call_back_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)))
+ -> decltype(_Call_back_binder(
+ _Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...)) {
+ return _Call_back_binder(_Seq{}, _Mypair._Get_first(), _Mypair._Myval2, _STD forward<_Unbound>(_Unbargs)...);
+ }
+
+ template
+ constexpr auto operator()(_Unbound&&... _Unbargs) && noexcept(noexcept(_Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)))
+ -> decltype(_Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)) {
+ return _Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...);
+ }
+
+ template
+ constexpr auto operator()(_Unbound&&... _Unbargs) const&& noexcept(noexcept(_Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)))
+ -> decltype(_Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...)) {
+ return _Call_back_binder(
+ _Seq{}, _STD move(_Mypair._Get_first()), _STD move(_Mypair._Myval2), _STD forward<_Unbound>(_Unbargs)...);
+ }
+};
+
+template
+_NODISCARD constexpr auto bind_back(_Fx&& _Func, _Types&&... _Args) {
+ static_assert(is_constructible_v, _Fx>,
+ "std::bind_back requires the decayed callable to be constructible from an undecayed callable");
+ static_assert(
+ is_move_constructible_v>, "std::bind_back requires the decayed callable to be move constructible");
+ static_assert(conjunction_v, _Types>...>,
+ "std::bind_back requires the decayed bound arguments to be constructible from undecayed bound arguments");
+ static_assert(conjunction_v>...>,
+ "std::bind_back requires the decayed bound arguments to be move constructible");
+
+ return _Back_binder, decay_t<_Types>...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
+}
+#endif // _HAS_CXX23
+
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
template
struct uses_allocator, _Alloc> : true_type {}; // true_type if container allocator enabled
diff --git a/stl/inc/ranges b/stl/inc/ranges
index edee6db4a..bbef590d2 100644
--- a/stl/inc/ranges
+++ b/stl/inc/ranges
@@ -61,108 +61,80 @@ namespace ranges {
using _Maybe_wrapped = conditional_t<_IsWrapped, _Ty, _Unwrapped_t<_Ty>>;
namespace _Pipe {
- // clang-format off
- template
- concept _Can_pipe = requires(_Left&& __l, _Right&& __r) {
- static_cast<_Right&&>(__r)(static_cast<_Left&&>(__l));
- };
-
- template
- concept _Can_compose = constructible_from, _Left>
- && constructible_from, _Right>;
- // clang-format on
-
- template
- struct _Pipeline;
-
template
- struct _Base {
- template
- requires _Can_compose<_Derived, _Other>
- constexpr auto operator|(_Base<_Other>&& __r) && noexcept(
- noexcept(_Pipeline{static_cast<_Derived&&>(*this), static_cast<_Other&&>(__r)})) {
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Derived, _Base<_Derived>>);
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Other, _Base<_Other>>);
- return _Pipeline{static_cast<_Derived&&>(*this), static_cast<_Other&&>(__r)};
- }
+ struct _Base {};
- template
- requires _Can_compose<_Derived, const _Other&>
- constexpr auto operator|(const _Base<_Other>& __r) && noexcept(
- noexcept(_Pipeline{static_cast<_Derived&&>(*this), static_cast(__r)})) {
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Derived, _Base<_Derived>>);
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Other, _Base<_Other>>);
- return _Pipeline{static_cast<_Derived&&>(*this), static_cast(__r)};
- }
+ template
+ _Ty* _Derived_from_range_adaptor_closure(_Base<_Ty>&); // not defined
- template
- requires _Can_compose
- constexpr auto operator|(_Base<_Other>&& __r) const& noexcept(
- noexcept(_Pipeline{static_cast(*this), static_cast<_Other&&>(__r)})) {
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Derived, _Base<_Derived>>);
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Other, _Base<_Other>>);
- return _Pipeline{static_cast(*this), static_cast<_Other&&>(__r)};
- }
-
- template
- requires _Can_compose
- constexpr auto operator|(const _Base<_Other>& __r) const& noexcept(
- noexcept(_Pipeline{static_cast(*this), static_cast(__r)})) {
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Derived, _Base<_Derived>>);
- _STL_INTERNAL_STATIC_ASSERT(derived_from<_Other, _Base<_Other>>);
- return _Pipeline{static_cast(*this), static_cast(__r)};
- }
-
- template <_Can_pipe _Left>
- friend constexpr auto operator|(_Left&& __l, const _Base& __r)
-#ifdef __EDG__ // TRANSITION, VSO-1222776
- noexcept(noexcept(_STD declval()(_STD forward<_Left>(__l))))
-#else // ^^^ workaround / no workaround vvv
- noexcept(noexcept(static_cast(__r)(_STD forward<_Left>(__l))))
-#endif // TRANSITION, VSO-1222776
- {
- return static_cast(__r)(_STD forward<_Left>(__l));
- }
-
- template <_Can_pipe<_Derived> _Left>
- friend constexpr auto operator|(_Left&& __l, _Base&& __r)
-#ifdef __EDG__ // TRANSITION, VSO-1222776
- noexcept(noexcept(_STD declval<_Derived>()(_STD forward<_Left>(__l))))
-#else // ^^^ workaround / no workaround vvv
- noexcept(noexcept(static_cast<_Derived&&>(__r)(_STD forward<_Left>(__l))))
-#endif // TRANSITION, VSO-1222776
- {
- return static_cast<_Derived&&>(__r)(_STD forward<_Left>(__l));
- }
+ template
+ concept _Range_adaptor_closure_object = !range> && requires(remove_cvref_t<_Ty> & __t) {
+ { _Pipe::_Derived_from_range_adaptor_closure(__t) } -> same_as*>;
};
- template
- struct _Pipeline : _Base<_Pipeline<_Left, _Right>> {
- /* [[no_unique_address]] */ _Left __l;
- /* [[no_unique_address]] */ _Right __r;
+ template
+ struct _Pipeline : _Base<_Pipeline<_ClosureLeft, _ClosureRight>> {
+ _STL_INTERNAL_STATIC_ASSERT(_Range_adaptor_closure_object<_ClosureLeft>);
+ _STL_INTERNAL_STATIC_ASSERT(_Range_adaptor_closure_object<_ClosureRight>);
+
+ /* [[no_unique_address]] */ _ClosureLeft _Left;
+ /* [[no_unique_address]] */ _ClosureRight _Right;
template
constexpr explicit _Pipeline(_Ty1&& _Val1, _Ty2&& _Val2) noexcept(
- is_nothrow_convertible_v<_Ty1, _Left>&& is_nothrow_convertible_v<_Ty2, _Right>)
- : __l(_STD forward<_Ty1>(_Val1)), __r(_STD forward<_Ty2>(_Val2)) {}
+ is_nothrow_constructible_v<_Ty1, _ClosureLeft>&& is_nothrow_constructible_v<_Ty2, _ClosureRight>)
+ : _Left(_STD forward<_Ty1>(_Val1)), _Right(_STD forward<_Ty2>(_Val2)) {}
template
- _NODISCARD constexpr auto operator()(_Ty&& _Val) noexcept(
- noexcept(__r(__l(_STD forward<_Ty>(_Val))))) requires requires {
- __r(__l(static_cast<_Ty&&>(_Val)));
+ _NODISCARD constexpr auto operator()(_Ty&& _Val) & noexcept(
+ noexcept(_Right(_Left(_STD forward<_Ty>(_Val))))) requires requires {
+ _Right(_Left(_STD forward<_Ty>(_Val)));
}
- { return __r(__l(_STD forward<_Ty>(_Val))); }
+ { return _Right(_Left(_STD forward<_Ty>(_Val))); }
template
- _NODISCARD constexpr auto operator()(_Ty&& _Val) const
- noexcept(noexcept(__r(__l(_STD forward<_Ty>(_Val))))) requires requires {
- __r(__l(static_cast<_Ty&&>(_Val)));
+ _NODISCARD constexpr auto operator()(_Ty&& _Val) const& noexcept(
+ noexcept(_Right(_Left(_STD forward<_Ty>(_Val))))) requires requires {
+ _Right(_Left(_STD forward<_Ty>(_Val)));
}
- { return __r(__l(_STD forward<_Ty>(_Val))); }
+ { return _Right(_Left(_STD forward<_Ty>(_Val))); }
+
+ template
+ _NODISCARD constexpr auto operator()(_Ty&& _Val) && noexcept(
+ noexcept(_STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val))))) requires requires {
+ _STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val)));
+ }
+ { return _STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val))); }
+
+ template
+ _NODISCARD constexpr auto operator()(_Ty&& _Val) const&& noexcept(
+ noexcept(_STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val))))) requires requires {
+ _STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val)));
+ }
+ { return _STD move(_Right)(_STD move(_Left)(_STD forward<_Ty>(_Val))); }
};
template
_Pipeline(_Ty1, _Ty2) -> _Pipeline<_Ty1, _Ty2>;
+
+ template
+ requires _Range_adaptor_closure_object<_Left> //
+ && _Range_adaptor_closure_object<_Right> //
+ && constructible_from, _Left> //
+ && constructible_from, _Right> //
+ _NODISCARD constexpr auto operator|(_Left&& __l, _Right&& __r) noexcept(
+ noexcept(_Pipeline{static_cast<_Left&&>(__l), static_cast<_Right&&>(__r)})) {
+ return _Pipeline{static_cast<_Left&&>(__l), static_cast<_Right&&>(__r)};
+ }
+
+ template
+ requires _Range_adaptor_closure_object<_Right> && range<_Left> //
+ _NODISCARD constexpr auto operator|(_Left&& __l, _Right&& __r) noexcept(
+ noexcept(_STD forward<_Right>(__r)(_STD forward<_Left>(__l)))) //
+ requires requires {
+ static_cast<_Right&&>(__r)(static_cast<_Left&&>(__l));
+ }
+ { return _STD forward<_Right>(__r)(_STD forward<_Left>(__l)); }
} // namespace _Pipe
template
@@ -805,6 +777,13 @@ namespace ranges {
};
};
+#if _HAS_CXX23
+ template
+ requires is_class_v<_Derived> && same_as<_Derived, remove_cv_t<_Derived>>
+ class range_adaptor_closure : public _Pipe::_Base<_Derived> {
+ };
+#endif // _HAS_CXX23
+
template
class _Range_closure : public _Pipe::_Base<_Range_closure<_Fn, _Types...>> {
public:
@@ -4289,7 +4268,6 @@ namespace ranges {
}
}
-
public:
using iterator_concept = typename _Outer_iter<_Const>::iterator_concept;
using value_type = range_value_t<_BaseTy>;
@@ -4506,7 +4484,7 @@ namespace ranges {
#else // ^^^ no workaround / workaround vvv
auto _Match = _RANGES search(subrange{_Current, _Last}, _Parent->_Pattern);
auto _Begin = _Match.begin();
- auto _End = _Match.end();
+ auto _End = _Match.end();
#endif // TRANSITION, DevCom-1559808
if (_Begin != _Last && _RANGES empty(_Parent->_Pattern)) {
++_Begin;
@@ -4558,7 +4536,7 @@ namespace ranges {
#else // ^^^ no workaround / workaround vvv
auto _Match = _RANGES search(subrange{_It, _Last}, _Pattern);
auto _Begin = _Match.begin();
- auto _End = _Match.end();
+ auto _End = _Match.end();
#endif // TRANSITION, DevCom-1559808
if (_Begin != _Last && _RANGES empty(_Pattern)) {
++_Begin;
@@ -5086,7 +5064,7 @@ namespace ranges {
(void) _Off;
#else // ^^^ _ITERATOR_DEBUG_LEVEL == 0 / _ITERATOR_DEBUG_LEVEL != 0 vvv
if constexpr (_Offset_verifiable_v>) {
- _Current._Verify_offset(_Off);
+ _Current._Verify_offset(_Off);
}
#endif // _ITERATOR_DEBUG_LEVEL == 0
}
diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h
index c5253c4ec..0b4a5d5d1 100644
--- a/stl/inc/yvals_core.h
+++ b/stl/inc/yvals_core.h
@@ -323,6 +323,7 @@
// P2302R4 ranges::contains, ranges::contains_subrange
// P2321R2 zip
// (changes to pair, tuple, and vector::reference only)
+// P2387R3 Pipe Support For User-Defined Range Adaptors
// P2417R2 More constexpr bitset
// P2440R1 ranges::iota, ranges::shift_left, ranges::shift_right
// P2441R2 views::join_with
@@ -1459,10 +1460,6 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
#define __cpp_lib_polymorphic_allocator 201902L
-#ifdef __cpp_lib_concepts // TRANSITION, GH-395
-#define __cpp_lib_ranges 202110L
-#endif // __cpp_lib_concepts
-
#define __cpp_lib_remove_cvref 201711L
#define __cpp_lib_semaphore 201907L
#define __cpp_lib_smart_ptr_for_overwrite 202002L
@@ -1495,6 +1492,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
#endif // __cpp_lib_concepts
#define __cpp_lib_associative_heterogeneous_erasure 202110L
+#define __cpp_lib_bind_back 202202L
#define __cpp_lib_byteswap 202110L
#define __cpp_lib_constexpr_bitset 202207L
#define __cpp_lib_constexpr_typeinfo 202106L
@@ -1569,6 +1567,14 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
#define __cpp_lib_optional 201606L // P0307R2 Making Optional Greater Equal Again
#endif // _HAS_CXX17
+#if defined(__cpp_lib_concepts) // TRANSITION, GH-395
+#if _HAS_CXX23
+#define __cpp_lib_ranges 202202L // P2387R3 Pipe Support For User-Defined Range Adaptors
+#elif _HAS_CXX20 // ^^^ _HAS_CXX23 / _HAS_CXX20 vvv
+#define __cpp_lib_ranges 202110L // P2415R2 What Is A `view`?
+#endif // _HAS_CXX20
+#endif // defined(__cpp_lib_concepts)
+
#if _HAS_CXX20
#define __cpp_lib_shared_ptr_arrays 201707L // P0674R1 make_shared() For Arrays
#else // _HAS_CXX20
diff --git a/tests/std/test.lst b/tests/std/test.lst
index 08211d1fc..55b773880 100644
--- a/tests/std/test.lst
+++ b/tests/std/test.lst
@@ -543,6 +543,8 @@ tests\P2273R3_constexpr_unique_ptr
tests\P2302R4_ranges_alg_contains
tests\P2302R4_ranges_alg_contains_subrange
tests\P2321R2_proxy_reference
+tests\P2387R3_bind_back
+tests\P2387R3_pipe_support_for_user_defined_range_adaptors
tests\P2401R0_conditional_noexcept_for_exchange
tests\P2408R5_ranges_iterators_to_classic_algorithms
tests\P2415R2_owning_view
diff --git a/tests/std/tests/P2387R3_bind_back/env.lst b/tests/std/tests/P2387R3_bind_back/env.lst
new file mode 100644
index 000000000..642f530ff
--- /dev/null
+++ b/tests/std/tests/P2387R3_bind_back/env.lst
@@ -0,0 +1,4 @@
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+RUNALL_INCLUDE ..\usual_latest_matrix.lst
diff --git a/tests/std/tests/P2387R3_bind_back/test.cpp b/tests/std/tests/P2387R3_bind_back/test.cpp
new file mode 100644
index 000000000..175c3497c
--- /dev/null
+++ b/tests/std/tests/P2387R3_bind_back/test.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+constexpr int f0() {
+ return 1729;
+}
+
+constexpr int f1(int x) {
+ return x * 10;
+}
+
+constexpr int f2(int x, int y) {
+ return x * 100 + y * 10;
+}
+
+constexpr int f3(int x, int y, int z) {
+ return x * 1000 + y * 100 + z * 10;
+}
+
+struct Cat {
+ string name;
+};
+
+struct CatNoise {
+ string noise(const string& s, const Cat& cat) const {
+ return cat.name + " says " + s;
+ }
+};
+
+struct DetectQualifiers {
+ constexpr string_view operator()() & {
+ return "modifiable lvalue";
+ }
+
+ constexpr string_view operator()() const& {
+ return "const lvalue";
+ }
+
+ constexpr string_view operator()() && {
+ return "modifiable rvalue";
+ }
+
+ constexpr string_view operator()() const&& {
+ return "const rvalue";
+ }
+};
+
+constexpr bool test_constexpr() {
+ // Test varying numbers of arguments.
+ assert(bind_back(f0)() == 1729);
+
+ assert(bind_back(f1)(2) == 20);
+ assert(bind_back(f1, 3)() == 30);
+
+ assert(bind_back(f2)(4, 5) == 450);
+ assert(bind_back(f2, 7)(6) == 670);
+ assert(bind_back(f2, 8, 9)() == 890);
+
+ assert(bind_back(f3)(2, 3, 4) == 2340);
+ assert(bind_back(f3, 4, 5)(3) == 3450);
+ assert(bind_back(f3, 5, 6)(4) == 4560);
+ assert(bind_back(f3, 5, 6, 7)() == 5670);
+
+ // Test function pointers.
+ assert(bind_back(&f0)() == 1729);
+ assert(bind_back(&f2, 7)(6) == 670);
+
+ // Test stateless lambdas.
+ assert(bind_back([] { return 11; })() == 11);
+ assert(bind_back([](int x, int y) { return x * 2 + y * 3; }, 10)(100) == 230);
+
+ // Test stateful lambdas.
+ int value = 0;
+
+ auto bound0 = bind_back([&value] { ++value; });
+ bound0();
+ assert(value == 1);
+ bound0();
+ assert(value == 2);
+
+ auto bound1 = bind_back([&value](int x, int y) { value = value * x + y; }, 10);
+ bound1(3);
+ assert(value == 16);
+ bound1(4);
+ assert(value == 74);
+
+ // Test "perfect forwarding call wrapper" behavior.
+ auto bound5 = bind_back(DetectQualifiers{});
+ assert(bound5() == "modifiable lvalue");
+ assert(as_const(bound5)() == "const lvalue");
+ assert(move(bound5)() == "modifiable rvalue");
+ assert(move(as_const(bound5))() == "const rvalue");
+
+ // Test decay when binding.
+ const int arr[] = {11, 22, 33};
+ const int three = 3;
+
+ auto bound8 = bind_back(
+ [](auto&& a, auto&& f, auto&& i) {
+ using FP = int (*)(int);
+ return is_same_v && is_same_v && is_same_v;
+ },
+ arr, f1, three);
+ assert(bound8());
+
+ // Test forward when calling.
+ auto bound9 = bind_back([](auto&& a1, auto&& a2, auto&& a3, auto&& a4) {
+ constexpr bool same1 = is_same_v;
+ constexpr bool same2 = is_same_v;
+ constexpr bool same3 = is_same_v;
+ constexpr bool same4 = is_same_v;
+ return same1 && same2 && same3 && same4;
+ });
+ assert(bound9(value, three, 1729, move(three)));
+
+ return true;
+}
+
+void test_move_only_types() {
+ // Test movable-only types.
+ auto unique_lambda = [up1 = make_unique(1200)](unique_ptr&& up2) {
+ if (up1 && up2) {
+ return make_unique(*up1 + *up2);
+ } else if (up1) {
+ return make_unique(*up1 * -1);
+ } else if (up2) {
+ return make_unique(*up2 * -10);
+ } else {
+ return make_unique(-9000);
+ }
+ };
+ auto bound6 = bind_back(move(unique_lambda), make_unique(34));
+ assert(*unique_lambda(make_unique(56)) == -560);
+ assert(*move(bound6)() == 1234);
+ auto bound7 = move(bound6);
+ assert(*move(bound6)() == -9000);
+ assert(*move(bound7)() == 1234);
+}
+
+int main() {
+ assert(test_constexpr());
+ static_assert(test_constexpr());
+
+ test_move_only_types();
+
+ // Also test GH-1292 "bind_front violates [func.require]p8" in which the return type of bind_front inadvertently
+ // depends on the value category and/or cv-qualification of its arguments.
+ {
+ struct S {
+ int i = 42;
+ };
+ S s;
+ auto lambda = [](S x) { return x.i; };
+ auto returns_lambda = [=] { return lambda; };
+ auto returns_const_lambda = [=]() -> const decltype(lambda) { return lambda; };
+ auto returns_const_S = []() -> const S { return {}; };
+
+ using T = decltype(bind_back(lambda, s));
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ static_assert(is_same_v);
+ }
+}
diff --git a/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/env.lst b/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/env.lst
new file mode 100644
index 000000000..8ac7033b2
--- /dev/null
+++ b/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/env.lst
@@ -0,0 +1,4 @@
+# Copyright (c) Microsoft Corporation.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+RUNALL_INCLUDE ..\strict_concepts_latest_matrix.lst
diff --git a/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/test.cpp b/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/test.cpp
new file mode 100644
index 000000000..ab0eac62d
--- /dev/null
+++ b/tests/std/tests/P2387R3_pipe_support_for_user_defined_range_adaptors/test.cpp
@@ -0,0 +1,236 @@
+// Copyright (c) Microsoft Corporation.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+template
+concept CanInstantiateRangeAdaptorClosure = requires {
+ typename ranges::range_adaptor_closure;
+};
+
+class EmptyTestType {};
+class IncompleteTestType;
+
+static_assert(CanInstantiateRangeAdaptorClosure);
+static_assert(!CanInstantiateRangeAdaptorClosure);
+static_assert(!CanInstantiateRangeAdaptorClosure);
+static_assert(!CanInstantiateRangeAdaptorClosure);
+static_assert(!CanInstantiateRangeAdaptorClosure);
+static_assert(CanInstantiateRangeAdaptorClosure);
+
+template
+concept CanPipe = requires(LHS lhs, RHS rhs) {
+ forward(lhs) | forward(rhs);
+};
+
+template
+concept CanPipe_R = requires(LHS lhs, RHS rhs) {
+ { forward(lhs) | forward(rhs) } -> same_as;
+};
+
+using TestRange = array;
+
+template
+constexpr bool is_range_adaptor_closure() {
+ return CanPipe || CanPipe //
+ || CanPipe || CanPipe;
+}
+
+struct IdentityRangeAdaptorClosure : ranges::range_adaptor_closure {
+ template
+ constexpr decltype(auto) operator()(T&& range) const {
+ return forward(range);
+ }
+};
+
+// Is not a range adaptor closure, because it is not a function object.
+struct NotCallable : ranges::range_adaptor_closure {};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it does not accept a range as argument.
+struct NotCallableWithRange : ranges::range_adaptor_closure {
+ void operator()() {}
+};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it doesn't derive from range_adaptor_closure.
+struct NotDerivedFrom {
+ void operator()(const TestRange&) {}
+};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it inherits privately from range_adaptor_closure.
+struct DerivedPrivately : private ranges::range_adaptor_closure {
+ void operator()(const TestRange&) {}
+};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it inherits from the wrong specialization of range_adaptor_closure.
+struct DerivedFromWrongSpecialization : ranges::range_adaptor_closure {
+ void operator()(const TestRange&) {}
+};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it has two base classes which are specializations of
+// range_adaptor_closure.
+struct DerivedFromTwoSpecializations : ranges::range_adaptor_closure,
+ ranges::range_adaptor_closure {
+ void operator()(const TestRange&) {}
+};
+static_assert(!is_range_adaptor_closure());
+
+// Is not a range adaptor closure, because it models ranges::range.
+struct ModelsRange : ranges::range_adaptor_closure {
+ void operator()(const TestRange&) {}
+
+ int* begin() {
+ return nullptr;
+ }
+ int* begin() const {
+ return nullptr;
+ }
+ int* end() {
+ return nullptr;
+ }
+ int* end() const {
+ return nullptr;
+ }
+};
+static_assert(ranges::range);
+static_assert(!is_range_adaptor_closure());
+
+struct RangeAdaptorClosureMemberRefQualTest : ranges::range_adaptor_closure {
+ constexpr ranges::empty_view operator()(const auto&) & {
+ return views::empty;
+ }
+
+ constexpr ranges::empty_view operator()(const auto&) && {
+ return views::empty;
+ }
+
+ constexpr ranges::empty_view operator()(const auto&) const& {
+ return views::empty;
+ }
+
+ constexpr ranges::empty_view operator()(const auto&) const&& {
+ return views::empty;
+ }
+};
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+
+using FirstIdentityThenMemberRefQualTest = decltype(
+ IdentityRangeAdaptorClosure{} | RangeAdaptorClosureMemberRefQualTest{});
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+
+using FirstTransformThenMemberRefQualTest = decltype(
+ views::transform([](auto x) { return x; }) | RangeAdaptorClosureMemberRefQualTest{});
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+
+using FirstMemberRefQualTestThenAll = decltype(RangeAdaptorClosureMemberRefQualTest{} | views::all);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+static_assert(CanPipe_R>);
+
+struct RangeAdaptorClosureParameterRefQualTest
+ : ranges::range_adaptor_closure {
+ constexpr char operator()(TestRange&) {
+ return {};
+ }
+
+ constexpr short operator()(TestRange&&) {
+ return {};
+ }
+
+ constexpr float operator()(const TestRange&) {
+ return {};
+ }
+
+ constexpr double operator()(const TestRange&&) {
+ return {};
+ }
+};
+static_assert(CanPipe_R);
+static_assert(CanPipe_R);
+static_assert(CanPipe_R);
+static_assert(CanPipe_R);
+
+struct MoveOnlyRangeAdaptorClosure : ranges::range_adaptor_closure {
+ MoveOnlyRangeAdaptorClosure() = default;
+ MoveOnlyRangeAdaptorClosure(const MoveOnlyRangeAdaptorClosure&) = delete;
+ MoveOnlyRangeAdaptorClosure(MoveOnlyRangeAdaptorClosure&&) = default;
+
+ void operator()(const TestRange&) {}
+};
+static_assert(CanPipe);
+static_assert(CanPipe);
+static_assert(CanPipe);
+static_assert(CanPipe);
+static_assert(!CanPipe);
+static_assert(!CanPipe);
+
+class TimesTwoAdaptor : public ranges::range_adaptor_closure {
+public:
+ template
+ constexpr auto operator()(R&& range) const {
+ return forward(range) | views::transform([](auto x) { return x * 2; });
+ }
+};
+
+class DividedByTwoAdaptor : public ranges::range_adaptor_closure {
+public:
+ template
+ constexpr auto operator()(R&& range) const {
+ return forward(range) | views::transform([](auto x) { return x / 2; });
+ }
+};
+
+constexpr bool test_user_defined_adaptors() {
+ const array numbers{1, 2, 3};
+ const array numbers_times_two{2, 4, 6};
+ assert(ranges::equal(numbers_times_two, numbers | TimesTwoAdaptor{}));
+ assert(ranges::equal(numbers, (numbers | TimesTwoAdaptor{}) | DividedByTwoAdaptor{}));
+ assert(ranges::equal(numbers, numbers | (TimesTwoAdaptor{} | DividedByTwoAdaptor{})));
+ assert(ranges::equal(numbers_times_two,
+ (numbers | TimesTwoAdaptor{})
+ | (TimesTwoAdaptor{} | TimesTwoAdaptor{} | DividedByTwoAdaptor{} | DividedByTwoAdaptor{})));
+
+ return true;
+}
+
+constexpr bool test_mixing_of_range_adaptors() {
+ const array numbers{1, 2, 3};
+ assert(ranges::equal(numbers, numbers | (TimesTwoAdaptor{} | views::transform([](int x) { return x / 2; }))));
+
+ const auto mixed_pipeline = TimesTwoAdaptor{} | views::reverse | DividedByTwoAdaptor{} | views::reverse;
+ assert(ranges::equal(mixed_pipeline(numbers), numbers));
+ assert(ranges::equal(numbers | mixed_pipeline, numbers));
+
+ auto factory_pipeline = views::iota(1) | TimesTwoAdaptor{} | views::take(3);
+ assert(ranges::equal(factory_pipeline, array{2, 4, 6}));
+
+ return true;
+}
+
+int main() {
+ assert(test_user_defined_adaptors());
+ static_assert(test_user_defined_adaptors());
+
+ assert(test_mixing_of_range_adaptors());
+ static_assert(test_mixing_of_range_adaptors());
+}
diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp
index bf41dcd99..8d20d002e 100644
--- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp
+++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp
@@ -272,6 +272,20 @@ STATIC_ASSERT(__cpp_lib_barrier == 201907L);
#endif
#endif
+#if _HAS_CXX23
+#ifndef __cpp_lib_bind_back
+#error __cpp_lib_bind_back is not defined
+#elif __cpp_lib_bind_back != 202202L
+#error __cpp_lib_bind_back is not 202202L
+#else
+STATIC_ASSERT(__cpp_lib_bind_back == 202202L);
+#endif
+#else
+#ifdef __cpp_lib_bind_back
+#error __cpp_lib_bind_back is defined
+#endif
+#endif
+
#if _HAS_CXX20
#ifndef __cpp_lib_bind_front
#error __cpp_lib_bind_front is not defined
@@ -1440,7 +1454,15 @@ STATIC_ASSERT(__cpp_lib_polymorphic_allocator == 201902L);
STATIC_ASSERT(__cpp_lib_quoted_string_io == 201304L);
#endif
-#if _HAS_CXX20 && defined(__cpp_lib_concepts) // TRANSITION, GH-395
+#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395
+#ifndef __cpp_lib_ranges
+#error __cpp_lib_ranges is not defined
+#elif __cpp_lib_ranges != 202202L
+#error __cpp_lib_ranges is not 202202L
+#else
+STATIC_ASSERT(__cpp_lib_ranges == 202202L);
+#endif
+#elif _HAS_CXX20 && defined(__cpp_lib_concepts) // TRANSITION, GH-395
#ifndef __cpp_lib_ranges
#error __cpp_lib_ranges is not defined
#elif __cpp_lib_ranges != 202110L