diff --git a/libcxx/include/__functional/reference_wrapper.h b/libcxx/include/__functional/reference_wrapper.h --- a/libcxx/include/__functional/reference_wrapper.h +++ b/libcxx/include/__functional/reference_wrapper.h @@ -10,11 +10,14 @@ #ifndef _LIBCPP___FUNCTIONAL_REFERENCE_WRAPPER_H #define _LIBCPP___FUNCTIONAL_REFERENCE_WRAPPER_H +#include "__concepts/convertible_to.h" #include <__config> #include <__functional/invoke.h> #include <__functional/weak_result_type.h> #include <__memory/addressof.h> +#include <__type_traits/common_reference.h> #include <__type_traits/enable_if.h> +#include <__type_traits/integral_constant.h> #include <__type_traits/remove_cvref.h> #include <__utility/declval.h> #include <__utility/forward.h> @@ -100,6 +103,50 @@ template void ref(const _Tp&&) = delete; template void cref(const _Tp&&) = delete; + +#if _LIBCPP_STD_VER >= 20 + +template +inline constexpr bool __is_ref_wrapper = false; + +template +inline constexpr bool __is_ref_wrapper> = true; + +// clang-format off + +template +concept __specialization_constraint = // CRW macro + // [1.1] first type is a reference_wrapper. + __is_ref_wrapper<_Rp> + // [1.2] common_reference with the underlying type exists + && requires { + typename common_reference_t; + } + // [1.3] reference_wrapper as specified is convertible to the above + && convertible_to<_RQ, common_reference_t> + ; + +template class _RQual, template class _TQual> + requires( __specialization_constraint<_Rp, _Tp, _RQual<_Rp>, _TQual<_Tp>> + // [1.4] commuted constraint should not match: + && !__specialization_constraint<_Tp, _Rp, _TQual<_Tp>, _RQual<_Rp>> ) +struct basic_common_reference<_Rp, _Tp, _RQual, _TQual> { + using type = common_reference_t>; +}; + + +template class _TQual, template class _RQual> + requires( __specialization_constraint<_Rp, _Tp, _RQual<_Rp>, _TQual<_Tp>> + // [1.4] commuted constraint should not match: + && !__specialization_constraint<_Tp, _Rp, _TQual<_Tp>, _RQual<_Rp>> ) +struct basic_common_reference<_Tp, _Rp, _TQual, _RQual> { + using type = common_reference_t>; +}; + +// clang-format on + +#endif + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___FUNCTIONAL_REFERENCE_WRAPPER_H diff --git a/libcxx/include/__type_traits/common_reference.h b/libcxx/include/__type_traits/common_reference.h --- a/libcxx/include/__type_traits/common_reference.h +++ b/libcxx/include/__type_traits/common_reference.h @@ -10,6 +10,7 @@ #define _LIBCPP___TYPE_TRAITS_COMMON_REFERENCE_H #include <__config> +#include <__type_traits/add_pointer.h> #include <__type_traits/common_type.h> #include <__type_traits/copy_cv.h> #include <__type_traits/copy_cvref.h> @@ -59,6 +60,8 @@ // COND-RES(COPYCV(X, Y)&, COPYCV(Y, X)&) if that type exists and is a reference type. template requires requires { typename __cv_cond_res<_Xp, _Yp>; } && is_reference_v<__cv_cond_res<_Xp, _Yp>> + && is_convertible_v<_Xp*, add_pointer_t<__cv_cond_res<_Xp, _Yp>>> + && is_convertible_v<_Yp*, add_pointer_t<__cv_cond_res<_Xp, _Yp>>> struct __common_ref<_Ap&, _Bp&, _Xp, _Yp> { using __type = __cv_cond_res<_Xp, _Yp>; @@ -76,6 +79,8 @@ requires { typename __common_ref_C<_Xp, _Yp>; } && is_convertible_v<_Ap&&, __common_ref_C<_Xp, _Yp>> && is_convertible_v<_Bp&&, __common_ref_C<_Xp, _Yp>> + && is_convertible_v<_Xp*, add_pointer_t<__common_ref_C<_Xp, _Yp>>> + && is_convertible_v<_Yp*, add_pointer_t<__common_ref_C<_Xp, _Yp>>> struct __common_ref<_Ap&&, _Bp&&, _Xp, _Yp> { using __type = __common_ref_C<_Xp, _Yp>; @@ -90,6 +95,8 @@ template requires requires { typename __common_ref_D<_Xp, _Yp>; } && is_convertible_v<_Ap&&, __common_ref_D<_Xp, _Yp>> + && is_convertible_v<_Xp*, add_pointer_t<__common_ref_D<_Xp, _Yp>>> + && is_convertible_v<_Yp*, add_pointer_t<__common_ref_D<_Xp, _Yp>>> struct __common_ref<_Ap&&, _Bp&, _Xp, _Yp> { using __type = __common_ref_D<_Xp, _Yp>; diff --git a/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/happy.compile.pass.cpp b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/happy.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/happy.compile.pass.cpp @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +#include "testutil.h" + +#include + +// +-----------------------------------+ +// | TESTS | +// +-----------------------------------+ + +// clang-format off +static_assert(check, int & >); +static_assert(check, int const& >); +static_assert(check, int & >); +static_assert(check, int const& >); +static_assert(check, const volatile int&>); + +// derived-base and implicit convertibles +struct B {}; +struct D : B {}; +struct C { + operator B&() const; +}; + +static_assert(check, D & >); +static_assert(check, D const&>); +static_assert(check, D const&>); + + +static_assert(check, B & >); +static_assert(check, B const&>); +static_assert(check, B const&>); +// Interesting note: This last set works on VC++ thanks to our specialization. +// But on GCC, it works naturally since ternary operator is no longer ambiguous. +// Here: +#ifndef _MSC_VER +static_assert(std::same_as, B&>>); +static_assert(std::same_as, B const &>>); +static_assert(std::same_as, B const&>>); +#else +// but for some reason ternary operator on VC++ yields a base prvalue! +static_assert(std::same_as, B&>>); +static_assert(std::same_as, B const &>>); +static_assert(std::same_as, B const&>>); +// fortunately, since this is not a reference type, common_reference rules fallthru +// basic_common_reference specialization, and we compute B& here. +#endif + + +// implicit conversions are two hops so shouldn't work: +static_assert( check_none, B& >); +static_assert( !std::convertible_to, B&>); + + +static_assert( check , C& >); +static_assert( check , C >); +static_assert( check, C >); +static_assert(!check , B& >); // Ref cannot be converted to B& + +// Hui: Tim's solution makes this one behaves more sensible (B& instead of const B&) +static_assert( check< B & , Ref , C const&>); // + +// clang-format on + +using Ri = Ref; +using RRi = Ref>; +using RRRi = Ref>>; +static_assert(check); +static_assert(check); +static_assert(check); +static_assert(check); + +static_assert(check_none); +static_assert(check_none); +static_assert(check_none); +static_assert(check_none); + +static_assert(check_none); +static_assert(check_none); diff --git a/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/sad.compile.pass.cpp b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/sad.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/sad.compile.pass.cpp @@ -0,0 +1,148 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +#include "testutil.h" +#include + + + +using namespace std; + + +// FACT: const& ref_wrapper can still be converted to int& +int i; +const std::reference_wrapper ri = i; +int& j = ri; // OK +static_assert(std::convertible_to const &, int &>); + +// ======================================================== +// Hui: Happy now +// ======================================================== +static_assert(check const &, int & >); + +// This is because of the first rule of common_reference, +// *before* it even instantiates the basic_common_reference: + +// Hui: Tim's solution will make CommonRef ill-formed, which makes it possible to use basic_common_reference +// static_assert(std::same_as const &, int &>> ); // CommonRef is from the standard, std::__common_ref in libc++ +static_assert(!hasCommonRef const&, int&>); + +// The reason CommonRef is no longer ill-formed is because of +// COPYCV https://eel.is/c++draft/meta.trans#other-2.3 +// which ends up copying the const from Ref const & to int, +// checking for const int & instead of int & +// This satisfies https://eel.is/c++draft/meta.trans#other-2.5 +static_assert(std::same_as const&, int const&>>); + + + + + + + +// --- gunk: + +static_assert(!HasType const&, int&>>); // ok, ternary is still ambiguous +static_assert( + std::convertible_to const&, int&>); // Ref const& CAN be converted to int& + +// ======================================================== +// Hui: Happy now +// ======================================================== +static_assert(check const&, int&>); // BUT we compute common ref as const int & + + +template +struct Test { + using R1 = common_reference_t; + using R2 = common_reference_t; + using R3 = common_reference_t; + using R4 = common_reference_t; + using R5 = common_reference_t; + + using U = reference_wrapper; + + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + + // commute: + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); + + // reference qualification of reference_wrapper is irrelevant (except KINKs + // we may need to resolve - see below) + static_assert(same_as>); + static_assert(same_as>); + // !! KINKS !! + +// ======================================================== +// Hui: What are they testing? I changed All of them to R1 and they pass +// ======================================================== + static_assert(same_as>); // NOT R1 which is non-const & + static_assert(same_as>); + static_assert(same_as>); +}; + +// Instantiate above checks: +template struct Test; +template struct Test>; + +// !! KINKS !! +// unfortunately, ?: is not ambigous with const & and && reference_wrapper so +// the specialiation is never consulted: + +// per https://eel.is/c++draft/meta.trans.other#2.5 +// common_reference< X&, Y const &> has const& for both sides of ternary-check: +using Kink = + decltype(false ? std::declval() : std::declval const&>()); +// the ?: is no longer ambigous and resolves to: +static_assert(same_as); + +// ======================================================== +// Hui: Kink no longer used in common_reference +// ======================================================== +static_assert(!same_as const&>>); +static_assert( same_as const&>>); + +// per https://eel.is/c++draft/meta.trans.other#2.7 +// common_reference checks for again, same as above: + +// ======================================================== +// Hui: Kink no longer used here +// ======================================================== +static_assert(!same_as&&>>); +static_assert( same_as&&>>); + + +///////////////////////////////////////////////////// +/// Unrelated cases involving reference_wrapper: /// +///////////////////////////////////////////////////// + +// reference_wrapper as both args is unaffected. unrelated to the proposal, +// subject to simple first rule of +static_assert(same_as&, reference_wrapper&>, + reference_wrapper&>); + +// double wrap is unaffected. unrelated to the proposal: +template +concept CommonRefWithNestedRefWrapExists = + requires(T) { typename common_reference_t>, T&>; }; +static_assert(!CommonRefWithNestedRefWrapExists); + + +static_assert( + same_as&, reference_wrapper>>, + reference_wrapper&>); diff --git a/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/testutil.h b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/testutil.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/function.objects/refwrap/refwrap.common_ref/testutil.h @@ -0,0 +1,70 @@ +#ifndef __LIBCPP_TEST_REFWRAPPER_UTIL_H +#define __LIBCPP_TEST_REFWRAPPER_UTIL_H + +#include +#include + +template +concept HasType = requires { + typename T::type; +}; + +template +concept check1 = std::same_as>; + +template +concept check2 = std::same_as>; + +template +concept check = check1 && check2; + +template +concept check_none1 = !HasType>; +template +concept check_none2 = !HasType>; + +template +concept check_none = check_none1 && check_none2; + +template +using Ref = std::reference_wrapper; + +// https://eel.is/c++draft/meta.trans#other-2.4 +template +using CondRes = decltype(false ? std::declval()() : std::declval()()); + +template +struct Ternary {}; + +template +requires requires() { typename CondRes; } +struct Ternary { + using type = CondRes; +}; +template +using Ternary_t = typename Ternary::type; + +// https://eel.is/c++draft/meta.trans#other-2.5 +template +using CopyCV = +#ifdef _MSC_VER + std::_Copy_cv; +#else + typename std::__copy_cv::type; +#endif + +template +using CommonRef = +#ifdef _MSC_VER + std::_Common_reference2A; // not quite but won't bother for now +#else + typename std::__common_ref::__type; +// https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/type_traits#L3652 +#endif + +template +concept hasCommonRef = requires{ + typename CommonRef; +}; + +#endif //__LIBCPP_TEST_REFWRAPPER_UTIL_H