diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv index bdfdfdd2d53c8..f6124d9a6c4e3 100644 --- a/libcxx/docs/Status/Cxx2cPapers.csv +++ b/libcxx/docs/Status/Cxx2cPapers.csv @@ -17,7 +17,7 @@ "`P0792R14 `__","LWG","``function_ref``: a type-erased callable reference","Varna June 2023","","","" "`P2874R2 `__","LWG","Mandating Annex D Require No More","Varna June 2023","","","" "`P2757R3 `__","LWG","Type-checking format args","Varna June 2023","","","|format|" -"`P2637R3 `__","LWG","Member ``visit``","Varna June 2023","","","|format|" +"`P2637R3 `__","LWG","Member ``visit``","Varna June 2023","|Partial|","18.0","" "`P2641R4 `__","CWG, LWG","Checking if a ``union`` alternative is active","Varna June 2023","","","" "`P1759R6 `__","LWG","Native handles and file streams","Varna June 2023","|Complete|","18.0","" "`P2697R1 `__","LWG","Interfacing ``bitset`` with ``string_view``","Varna June 2023","|Complete|","18.0","" diff --git a/libcxx/include/__config b/libcxx/include/__config index 90a4585938a13..2825d03b7c205 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -1513,6 +1513,11 @@ __sanitizer_verify_double_ended_contiguous_container(const void*, const void*, c # define _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK # endif +// Clang-18 has support for deducing this, but it does not set the FTM. +# if defined(__cpp_explicit_this_parameter) || (defined(_LIBCPP_CLANG_VER) && _LIBCPP_CLANG_VER >= 1800) +# define _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER +# endif + #endif // __cplusplus #endif // _LIBCPP___CONFIG diff --git a/libcxx/include/variant b/libcxx/include/variant index 6179b2a1a0ab6..ac69645a0fab0 100644 --- a/libcxx/include/variant +++ b/libcxx/include/variant @@ -69,6 +69,12 @@ namespace std { // 20.7.2.6, swap void swap(variant&) noexcept(see below); + + // [variant.visit], visitation + template + constexpr decltype(auto) visit(this Self&&, Visitor&&); // Since C++26 + template + constexpr R visit(this Self&&, Visitor&&); // Since C++26 }; // 20.7.3, variant helper classes @@ -235,6 +241,7 @@ namespace std { #include <__type_traits/void_t.h> #include <__utility/declval.h> #include <__utility/forward.h> +#include <__utility/forward_like.h> #include <__utility/in_place.h> #include <__utility/move.h> #include <__utility/swap.h> @@ -1130,6 +1137,19 @@ using __best_match_t = typename invoke_result_t<_MakeOverloads<_Types...>, _Tp, } // namespace __variant_detail +template ()))...>> +_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr decltype(auto) +visit(_Visitor&& __visitor, _Vs&&... __vs); + +# if _LIBCPP_STD_VER >= 20 +template ()))...>> +_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr _Rp +visit(_Visitor&& __visitor, _Vs&&... __vs); +# endif + template class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES variant : private __sfinae_ctor_base< __all...>::value, @@ -1273,6 +1293,27 @@ public: __impl_.__swap(__that.__impl_); } +# if _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + // Helper class to implement [variant.visit]/10 + // Constraints: The call to visit does not use an explicit template-argument-list + // that begins with a type template-argument. + struct __variant_visit_barrier_tag { + _LIBCPP_HIDE_FROM_ABI explicit __variant_visit_barrier_tag() = default; + }; + + template <__variant_visit_barrier_tag = __variant_visit_barrier_tag{}, class _Self, class _Visitor> + _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) visit(this _Self&& __self, _Visitor&& __visitor) { + using _VariantT = _OverrideRef<_Self&&, _CopyConst, variant>>; + return std::visit(std::forward<_Visitor>(__visitor), (_VariantT)__self); + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr _Rp visit(this _Self&& __self, _Visitor&& __visitor) { + using _VariantT = _OverrideRef<_Self&&, _CopyConst, variant>>; + return std::visit<_Rp>(std::forward<_Visitor>(__visitor), (_VariantT)__self); + } +# endif + private: __variant_detail::__impl<_Types...> __impl_; @@ -1511,7 +1552,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr vo } } -template < class _Visitor, class... _Vs, typename = void_t()))...> > +template < class _Visitor, class... _Vs, typename> _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr decltype(auto) visit(_Visitor&& __visitor, _Vs&&... __vs) { using __variant_detail::__visitation::__variant; @@ -1520,10 +1561,7 @@ visit(_Visitor&& __visitor, _Vs&&... __vs) { } # if _LIBCPP_STD_VER >= 20 -template < class _Rp, - class _Visitor, - class... _Vs, - typename = void_t()))...> > +template < class _Rp, class _Visitor, class... _Vs, typename> _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr _Rp visit(_Visitor&& __visitor, _Vs&&... __vs) { using __variant_detail::__visitation::__variant; diff --git a/libcxx/test/std/utilities/variant/variant.visit.member/robust_against_adl.pass.cpp b/libcxx/test/std/utilities/variant/variant.visit.member/robust_against_adl.pass.cpp new file mode 100644 index 0000000000000..c54f2b722d46a --- /dev/null +++ b/libcxx/test/std/utilities/variant/variant.visit.member/robust_against_adl.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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, c++20, c++23 +// The tested functionality needs deducing this. +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// class variant; +// template +// constexpr decltype(auto) visit(this Self&&, Visitor&&); // since C++26 +// template +// constexpr R visit(this Self&&, Visitor&&); // since C++26 + +#include + +#include "test_macros.h" + +struct Incomplete; +template +struct Holder { + T t; +}; + +constexpr bool test(bool do_it) { + if (do_it) { + std::variant*, int> v = nullptr; + + v.visit([](auto) {}); + v.visit([](auto) -> Holder* { return nullptr; }); + v.visit([](auto) {}); + v.visit([](auto) -> Holder* { return nullptr; }); + } + return true; +} + +int main(int, char**) { + test(true); + static_assert(test(true)); + + return 0; +} diff --git a/libcxx/test/std/utilities/variant/variant.visit.member/visit.pass.cpp b/libcxx/test/std/utilities/variant/variant.visit.member/visit.pass.cpp new file mode 100644 index 0000000000000..68706d6c32af4 --- /dev/null +++ b/libcxx/test/std/utilities/variant/variant.visit.member/visit.pass.cpp @@ -0,0 +1,258 @@ +//===----------------------------------------------------------------------===// +// +// 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, c++20, c++23 +// The tested functionality needs deducing this. +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// class variant; + +// template +// constexpr decltype(auto) visit(this Self&&, Visitor&&); // since C++26 + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "variant_test_helpers.h" + +void test_call_operator_forwarding() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn& cobj = obj; + + { // test call operator forwarding - single variant, single arg + using V = std::variant; + V v(42); + + v.visit(obj); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + v.visit(cobj); + assert(Fn::check_call(CT_Const | CT_LValue)); + v.visit(std::move(obj)); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + v.visit(std::move(cobj)); + assert(Fn::check_call(CT_Const | CT_RValue)); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42L); + + v.visit(obj); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + v.visit(cobj); + assert(Fn::check_call(CT_Const | CT_LValue)); + v.visit(std::move(obj)); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + v.visit(std::move(cobj)); + assert(Fn::check_call(CT_Const | CT_RValue)); + } +} + +// Applies to non-member `std::visit` only. +void test_argument_forwarding() { + using Fn = ForwardingCallObject; + Fn obj{}; + const auto val = CT_LValue | CT_NonConst; + + { // single argument - value type + using V = std::variant; + V v(42); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cv.visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + } +#if !defined(TEST_VARIANT_HAS_NO_REFERENCES) + { // single argument - lvalue reference + using V = std::variant; + int x = 42; + V v(x); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cv.visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + assert(false); + } + { // single argument - rvalue reference + using V = std::variant; + int x = 42; + V v(std::move(x)); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cvstd::visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + } +#endif +} + +void test_return_type() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn& cobj = obj; + + { // test call operator forwarding - single variant, single arg + using V = std::variant; + V v(42); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42L); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } +} + +void test_constexpr() { + constexpr ReturnFirst obj{}; + + { + using V = std::variant; + constexpr V v(42); + + static_assert(v.visit(obj) == 42); + } + { + using V = std::variant; + constexpr V v(42L); + + static_assert(v.visit(obj) == 42); + } +} + +void test_exceptions() { +#ifndef TEST_HAS_NO_EXCEPTIONS + ReturnArity obj{}; + + auto test = [&](auto&& v) { + try { + v.visit(obj); + } catch (const std::bad_variant_access&) { + return true; + } catch (...) { + } + return false; + }; + + { + using V = std::variant; + V v; + makeEmpty(v); + + assert(test(v)); + } +#endif +} + +// See https://llvm.org/PR31916 +void test_caller_accepts_nonconst() { + struct A {}; + struct Visitor { + void operator()(A&) {} + }; + std::variant v; + + v.visit(Visitor{}); +} + +struct MyVariant : std::variant {}; + +namespace std { +template +void get(const MyVariant&) { + assert(false); +} +} // namespace std + +void test_derived_from_variant() { + auto v1 = MyVariant{42}; + const auto cv1 = MyVariant{142}; + + v1.visit([](auto x) { assert(x == 42); }); + cv1.visit([](auto x) { assert(x == 142); }); + MyVariant{-1.25f}.visit([](auto x) { assert(x == -1.25f); }); + std::move(v1).visit([](auto x) { assert(x == 42); }); + std::move(cv1).visit([](auto x) { assert(x == 142); }); + + // Check that visit does not take index nor valueless_by_exception members from the base class. + struct EvilVariantBase { + int index; + char valueless_by_exception; + }; + + struct EvilVariant1 : std::variant, std::tuple, EvilVariantBase { + using std::variant::variant; + }; + + EvilVariant1{12}.visit([](auto x) { assert(x == 12); }); + EvilVariant1{12.3}.visit([](auto x) { assert(x == 12.3); }); + + // Check that visit unambiguously picks the variant, even if the other base has __impl member. + struct ImplVariantBase { + struct Callable { + bool operator()() const { + assert(false); + return false; + } + }; + + Callable __impl; + }; + + struct EvilVariant2 : std::variant, ImplVariantBase { + using std::variant::variant; + }; + + EvilVariant2{12}.visit([](auto x) { assert(x == 12); }); + EvilVariant2{12.3}.visit([](auto x) { assert(x == 12.3); }); +} + +int main(int, char**) { + test_call_operator_forwarding(); + test_argument_forwarding(); + test_return_type(); + test_constexpr(); + test_exceptions(); + test_caller_accepts_nonconst(); + test_derived_from_variant(); + + return 0; +} diff --git a/libcxx/test/std/utilities/variant/variant.visit.member/visit_return_type.pass.cpp b/libcxx/test/std/utilities/variant/variant.visit.member/visit_return_type.pass.cpp new file mode 100644 index 0000000000000..20472c62fc5f9 --- /dev/null +++ b/libcxx/test/std/utilities/variant/variant.visit.member/visit_return_type.pass.cpp @@ -0,0 +1,345 @@ +//===----------------------------------------------------------------------===// +// +// 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, c++20, c++23 +// The tested functionality needs deducing this. +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// class variant; + +// template +// constexpr R visit(this Self&&, Visitor&&); // since C++26 + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "variant_test_helpers.h" + +template +struct overloaded : Ts... { + using Ts::operator()...; +}; + +void test_overload_ambiguity() { + using V = std::variant; + using namespace std::string_literals; + V v{"baba"s}; + + v.visit( + overloaded{[]([[maybe_unused]] auto x) { assert(false); }, [](const std::string& x) { assert(x == "baba"s); }}); + assert(std::get(v) == "baba"s); + + // Test the constraint. + v = std::move(v).visit(overloaded{ + []([[maybe_unused]] auto x) { + assert(false); + return 0; + }, + [](const std::string& x) { + assert(x == "baba"s); + return x + " zmt"s; + }}); + assert(std::get(v) == "baba zmt"s); +} + +template +void test_call_operator_forwarding() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn& cobj = obj; + + { // test call operator forwarding - single variant, single arg + using V = std::variant; + V v(42); + + v.visit(obj); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + v.visit(cobj); + assert(Fn::check_call(CT_Const | CT_LValue)); + v.visit(std::move(obj)); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + v.visit(std::move(cobj)); + assert(Fn::check_call(CT_Const | CT_RValue)); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42L); + + v.visit(obj); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + v.visit(cobj); + assert(Fn::check_call(CT_Const | CT_LValue)); + v.visit(std::move(obj)); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + v.visit(std::move(cobj)); + assert(Fn::check_call(CT_Const | CT_RValue)); + } +} + +template +void test_argument_forwarding() { + using Fn = ForwardingCallObject; + Fn obj{}; + const auto val = CT_LValue | CT_NonConst; + + { // single argument - value type + using V = std::variant; + V v(42); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cv.visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + } +#if !defined(TEST_VARIANT_HAS_NO_REFERENCES) + { // single argument - lvalue reference + using V = std::variant; + int x = 42; + V v(x); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cv.visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + } + { // single argument - rvalue reference + using V = std::variant; + int x = 42; + V v(std::move(x)); + const V& cv = v; + + v.visit(obj); + assert(Fn::check_call(val)); + cv.visit(obj); + assert(Fn::check_call(val)); + std::move(v).visit(obj); + assert(Fn::check_call(val)); + std::move(cv).visit(obj); + assert(Fn::check_call(val)); + } +#endif +} + +template +void test_return_type() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn& cobj = obj; + + { // test call operator forwarding - no variant + // non-member + { + static_assert(std::is_same_v(obj)), ReturnType>); + static_assert(std::is_same_v(cobj)), ReturnType>); + static_assert(std::is_same_v(std::move(obj))), ReturnType>); + static_assert(std::is_same_v(std::move(cobj))), ReturnType>); + } + } + { // test call operator forwarding - single variant, single arg + using V = std::variant; + V v(42); + + static_assert(std::is_same_v(obj)), ReturnType>); + static_assert(std::is_same_v(cobj)), ReturnType>); + static_assert(std::is_same_v(std::move(obj))), ReturnType>); + static_assert(std::is_same_v(std::move(cobj))), ReturnType>); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42L); + + static_assert(std::is_same_v(obj)), ReturnType>); + static_assert(std::is_same_v(cobj)), ReturnType>); + static_assert(std::is_same_v(std::move(obj))), ReturnType>); + static_assert(std::is_same_v(std::move(cobj))), ReturnType>); + } +} + +void test_constexpr_void() { + constexpr ReturnFirst obj{}; + + { + using V = std::variant; + constexpr V v(42); + + static_assert((v.visit(obj), 42) == 42); + } + { + using V = std::variant; + constexpr V v(42L); + + static_assert((v.visit(obj), 42) == 42); + } +} + +void test_constexpr_int() { + constexpr ReturnFirst obj{}; + + { + using V = std::variant; + constexpr V v(42); + + static_assert(v.visit(obj) == 42); + } + { + using V = std::variant; + constexpr V v(42L); + + static_assert(v.visit(obj) == 42); + } +} + +template +void test_exceptions() { +#ifndef TEST_HAS_NO_EXCEPTIONS + ReturnArity obj{}; + + auto test = [&](auto&& v) { + try { + v.template visit(obj); + } catch (const std::bad_variant_access&) { + return true; + } catch (...) { + } + return false; + }; + + { + using V = std::variant; + V v; + makeEmpty(v); + + assert(test(v)); + } +#endif +} + +// See https://bugs.llvm.org/show_bug.cgi?id=31916 +template +void test_caller_accepts_nonconst() { + struct A {}; + struct Visitor { + auto operator()(A&) { + if constexpr (!std::is_void_v) { + return ReturnType{}; + } + } + }; + std::variant v; + + v.template visit(Visitor{}); +} + +void test_constexpr_explicit_side_effect() { + auto test_lambda = [](int arg) constexpr { + std::variant v = 101; + + { + v.template visit([arg](int& x) constexpr { x = arg; }); + } + + return std::get(v); + }; + + static_assert(test_lambda(202) == 202); +} + +void test_derived_from_variant() { + struct MyVariant : std::variant {}; + + MyVariant{42}.template visit([](auto x) { + assert(x == 42); + return true; + }); + MyVariant{-1.3f}.template visit([](auto x) { + assert(x == -1.3f); + return true; + }); + + // Check that visit does not take index nor valueless_by_exception members from the base class. + struct EvilVariantBase { + int index; + char valueless_by_exception; + }; + + struct EvilVariant1 : std::variant, std::tuple, EvilVariantBase { + using std::variant::variant; + }; + + EvilVariant1{12}.template visit([](auto x) { + assert(x == 12); + return true; + }); + EvilVariant1{12.3}.template visit([](auto x) { + assert(x == 12.3); + return true; + }); + + // Check that visit unambiguously picks the variant, even if the other base has __impl member. + struct ImplVariantBase { + struct Callable { + bool operator()() const { + assert(false); + return false; + } + }; + + Callable __impl; + }; + + struct EvilVariant2 : std::variant, ImplVariantBase { + using std::variant::variant; + }; + + EvilVariant2{12}.template visit([](auto x) { + assert(x == 12); + return true; + }); + EvilVariant2{12.3}.template visit([](auto x) { + assert(x == 12.3); + return true; + }); +} + +int main(int, char**) { + test_overload_ambiguity(); + test_call_operator_forwarding(); + test_argument_forwarding(); + test_return_type(); + test_constexpr_void(); + test_exceptions(); + test_caller_accepts_nonconst(); + test_call_operator_forwarding(); + test_argument_forwarding(); + test_return_type(); + test_constexpr_int(); + test_exceptions(); + test_caller_accepts_nonconst(); + test_constexpr_explicit_side_effect(); + test_derived_from_variant(); + + return 0; +} diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index 958c34edde005..a5df187b046f6 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1267,7 +1267,11 @@ def add_version_header(tc): }, { "name": "__cpp_lib_variant", - "values": {"c++17": 202102}, + "values": { + "c++17": 202102, # std::visit for classes derived from std::variant + # "c++20": 202106, # Fully constexpr std::variant + # "c++26": 202306, # Member visit (implemented) + }, "headers": ["variant"], }, {