[libcxx-commits] [libcxx] 3412bc7 - [libc++][variant] P2637R3: Member `visit` (`std::variant`) (#76447)

via libcxx-commits libcxx-commits at lists.llvm.org
Sat Jan 20 21:16:55 PST 2024


Author: Hristo Hristov
Date: 2024-01-21T07:16:51+02:00
New Revision: 3412bc765887d2b2244e4338adc2f49420cce9c8

URL: https://github.com/llvm/llvm-project/commit/3412bc765887d2b2244e4338adc2f49420cce9c8
DIFF: https://github.com/llvm/llvm-project/commit/3412bc765887d2b2244e4338adc2f49420cce9c8.diff

LOG: [libc++][variant] P2637R3: Member `visit` (`std::variant`) (#76447)

Implements parts of: `P2637R3` https://wg21.link/P2637R3
(https://eel.is/c++draft/variant.visit)

Implements:
`variant.visit()`
`variant.visit<R>()`

The tests are as close as possible to the non-member function.

To land after: https://github.com/llvm/llvm-project/pull/76268

---------

Co-authored-by: Zingam <zingam at outlook.com>

Added: 
    libcxx/test/std/utilities/variant/variant.visit.member/robust_against_adl.pass.cpp
    libcxx/test/std/utilities/variant/variant.visit.member/visit.pass.cpp
    libcxx/test/std/utilities/variant/variant.visit.member/visit_return_type.pass.cpp

Modified: 
    libcxx/docs/Status/Cxx2cPapers.csv
    libcxx/include/__config
    libcxx/include/variant
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index bdfdfdd2d53c80..f6124d9a6c4e38 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -17,7 +17,7 @@
 "`P0792R14 <https://wg21.link/P0792R14>`__","LWG","``function_ref``: a type-erased callable reference","Varna June 2023","","",""
 "`P2874R2 <https://wg21.link/P2874R2>`__","LWG","Mandating Annex D Require No More","Varna June 2023","","",""
 "`P2757R3 <https://wg21.link/P2757R3>`__","LWG","Type-checking format args","Varna June 2023","","","|format|"
-"`P2637R3 <https://wg21.link/P2637R3>`__","LWG","Member ``visit``","Varna June 2023","","","|format|"
+"`P2637R3 <https://wg21.link/P2637R3>`__","LWG","Member ``visit``","Varna June 2023","|Partial|","18.0",""
 "`P2641R4 <https://wg21.link/P2641R4>`__","CWG, LWG","Checking if a ``union`` alternative is active","Varna June 2023","","",""
 "`P1759R6 <https://wg21.link/P1759R6>`__","LWG","Native handles and file streams","Varna June 2023","|Complete|","18.0",""
 "`P2697R1 <https://wg21.link/P2697R1>`__","LWG","Interfacing ``bitset`` with ``string_view``","Varna June 2023","|Complete|","18.0",""

diff  --git a/libcxx/include/__config b/libcxx/include/__config
index 90a4585938a13f..2825d03b7c2052 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 6179b2a1a0ab61..ac69645a0fab0d 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<class Self, class Visitor>
+      constexpr decltype(auto) visit(this Self&&, Visitor&&); // Since C++26
+    template<class R, class Self, class Visitor>
+      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 <class _Visitor, class... _Vs, typename = void_t<decltype(std::__as_variant(std::declval<_Vs>()))...>>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr decltype(auto)
+visit(_Visitor&& __visitor, _Vs&&... __vs);
+
+#  if _LIBCPP_STD_VER >= 20
+template <class _Rp,
+          class _Visitor,
+          class... _Vs,
+          typename = void_t<decltype(std::__as_variant(std::declval<_Vs>()))...>>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr _Rp
+visit(_Visitor&& __visitor, _Vs&&... __vs);
+#  endif
+
 template <class... _Types>
 class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES variant
     : private __sfinae_ctor_base< __all<is_copy_constructible_v<_Types>...>::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<remove_reference_t<_Self>, variant>>;
+    return std::visit(std::forward<_Visitor>(__visitor), (_VariantT)__self);
+  }
+
+  template <class _Rp, class _Self, class _Visitor>
+  _LIBCPP_HIDE_FROM_ABI constexpr _Rp visit(this _Self&& __self, _Visitor&& __visitor) {
+    using _VariantT = _OverrideRef<_Self&&, _CopyConst<remove_reference_t<_Self>, 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<decltype(std::__as_variant(std::declval<_Vs>()))...> >
+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<decltype(std::__as_variant(std::declval<_Vs>()))...> >
+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 00000000000000..c54f2b722d46a9
--- /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
+
+// <variant>
+
+// class variant;
+// template<class Self, class Visitor>
+//   constexpr decltype(auto) visit(this Self&&, Visitor&&); // since C++26
+// template<class R, class Self, class Visitor>
+//   constexpr R visit(this Self&&, Visitor&&);              // since C++26
+
+#include <variant>
+
+#include "test_macros.h"
+
+struct Incomplete;
+template <class T>
+struct Holder {
+  T t;
+};
+
+constexpr bool test(bool do_it) {
+  if (do_it) {
+    std::variant<Holder<Incomplete>*, int> v = nullptr;
+
+    v.visit([](auto) {});
+    v.visit([](auto) -> Holder<Incomplete>* { return nullptr; });
+    v.visit<void>([](auto) {});
+    v.visit<void*>([](auto) -> Holder<Incomplete>* { 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 00000000000000..68706d6c32af4f
--- /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
+
+// <variant>
+
+// class variant;
+
+// template<class Self, class Visitor>
+//   constexpr decltype(auto) visit(this Self&&, Visitor&&); // since C++26
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+#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<int>;
+    V v(42);
+
+    v.visit(obj);
+    assert(Fn::check_call<int&>(CT_NonConst | CT_LValue));
+    v.visit(cobj);
+    assert(Fn::check_call<int&>(CT_Const | CT_LValue));
+    v.visit(std::move(obj));
+    assert(Fn::check_call<int&>(CT_NonConst | CT_RValue));
+    v.visit(std::move(cobj));
+    assert(Fn::check_call<int&>(CT_Const | CT_RValue));
+  }
+  { // test call operator forwarding - single variant, multi arg
+    using V = std::variant<int, long, double>;
+    V v(42L);
+
+    v.visit(obj);
+    assert(Fn::check_call<long&>(CT_NonConst | CT_LValue));
+    v.visit(cobj);
+    assert(Fn::check_call<long&>(CT_Const | CT_LValue));
+    v.visit(std::move(obj));
+    assert(Fn::check_call<long&>(CT_NonConst | CT_RValue));
+    v.visit(std::move(cobj));
+    assert(Fn::check_call<long&>(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<int>;
+    V v(42);
+    const V& cv = v;
+
+    v.visit(obj);
+    assert(Fn::check_call<int&>(val));
+    cv.visit(obj);
+    assert(Fn::check_call<const int&>(val));
+    std::move(v).visit(obj);
+    assert(Fn::check_call<int&&>(val));
+    std::move(cv).visit(obj);
+    assert(Fn::check_call<const int&&>(val));
+  }
+#if !defined(TEST_VARIANT_HAS_NO_REFERENCES)
+  { // single argument - lvalue reference
+    using V = std::variant<int&>;
+    int x   = 42;
+    V v(x);
+    const V& cv = v;
+
+    v.visit(obj);
+    assert(Fn::check_call<int&>(val));
+    cv.visit(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(v).visit(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(cv).visit(obj);
+    assert(Fn::check_call<int&>(val));
+    assert(false);
+  }
+  { // single argument - rvalue reference
+    using V = std::variant<int&&>;
+    int x   = 42;
+    V v(std::move(x));
+    const V& cv = v;
+
+    v.visit(obj);
+    assert(Fn::check_call<int&>(val));
+    cvstd::visit(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(v).visit(obj);
+    assert(Fn::check_call<int&&>(val));
+    std::move(cv).visit(obj);
+    assert(Fn::check_call<int&&>(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<int>;
+    V v(42);
+
+    static_assert(std::is_same_v<decltype(v.visit(obj)), Fn&>);
+    static_assert(std::is_same_v<decltype(v.visit(cobj)), const Fn&>);
+    static_assert(std::is_same_v<decltype(v.visit(std::move(obj))), Fn&&>);
+    static_assert(std::is_same_v<decltype(v.visit(std::move(cobj))), const Fn&&>);
+  }
+  { // test call operator forwarding - single variant, multi arg
+    using V = std::variant<int, long, double>;
+    V v(42L);
+
+    static_assert(std::is_same_v<decltype(v.visit(obj)), Fn&>);
+    static_assert(std::is_same_v<decltype(v.visit(cobj)), const Fn&>);
+    static_assert(std::is_same_v<decltype(v.visit(std::move(obj))), Fn&&>);
+    static_assert(std::is_same_v<decltype(v.visit(std::move(cobj))), const Fn&&>);
+  }
+}
+
+void test_constexpr() {
+  constexpr ReturnFirst obj{};
+
+  {
+    using V = std::variant<int>;
+    constexpr V v(42);
+
+    static_assert(v.visit(obj) == 42);
+  }
+  {
+    using V = std::variant<short, long, char>;
+    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<int, MakeEmptyT>;
+    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<A> v;
+
+  v.visit(Visitor{});
+}
+
+struct MyVariant : std::variant<short, long, float> {};
+
+namespace std {
+template <std::size_t Index>
+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<int, long, double>, std::tuple<int>, EvilVariantBase {
+    using std::variant<int, long, double>::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<int, long, double>, ImplVariantBase {
+    using std::variant<int, long, double>::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 00000000000000..20472c62fc5f98
--- /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
+
+// <variant>
+
+// class variant;
+
+// template<class R, class Self, class Visitor>
+//   constexpr R visit(this Self&&, Visitor&&);              // since C++26
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+#include "test_macros.h"
+#include "variant_test_helpers.h"
+
+template <class... Ts>
+struct overloaded : Ts... {
+  using Ts::operator()...;
+};
+
+void test_overload_ambiguity() {
+  using V = std::variant<float, long, std::string>;
+  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<std::string>(v) == "baba"s);
+
+  // Test the constraint.
+  v = std::move(v).visit<V>(overloaded{
+      []([[maybe_unused]] auto x) {
+        assert(false);
+        return 0;
+      },
+      [](const std::string& x) {
+        assert(x == "baba"s);
+        return x + " zmt"s;
+      }});
+  assert(std::get<std::string>(v) == "baba zmt"s);
+}
+
+template <typename ReturnType>
+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<int>;
+    V v(42);
+
+    v.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(CT_NonConst | CT_LValue));
+    v.visit<ReturnType>(cobj);
+    assert(Fn::check_call<int&>(CT_Const | CT_LValue));
+    v.visit<ReturnType>(std::move(obj));
+    assert(Fn::check_call<int&>(CT_NonConst | CT_RValue));
+    v.visit<ReturnType>(std::move(cobj));
+    assert(Fn::check_call<int&>(CT_Const | CT_RValue));
+  }
+  { // test call operator forwarding - single variant, multi arg
+    using V = std::variant<int, long, double>;
+    V v(42L);
+
+    v.visit<ReturnType>(obj);
+    assert(Fn::check_call<long&>(CT_NonConst | CT_LValue));
+    v.visit<ReturnType>(cobj);
+    assert(Fn::check_call<long&>(CT_Const | CT_LValue));
+    v.visit<ReturnType>(std::move(obj));
+    assert(Fn::check_call<long&>(CT_NonConst | CT_RValue));
+    v.visit<ReturnType>(std::move(cobj));
+    assert(Fn::check_call<long&>(CT_Const | CT_RValue));
+  }
+}
+
+template <typename ReturnType>
+void test_argument_forwarding() {
+  using Fn = ForwardingCallObject;
+  Fn obj{};
+  const auto val = CT_LValue | CT_NonConst;
+
+  { // single argument - value type
+    using V = std::variant<int>;
+    V v(42);
+    const V& cv = v;
+
+    v.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    cv.visit<ReturnType>(obj);
+    assert(Fn::check_call<const int&>(val));
+    std::move(v).visit<ReturnType>(obj);
+    assert(Fn::check_call<int&&>(val));
+    std::move(cv).visit<ReturnType>(obj);
+    assert(Fn::check_call<const int&&>(val));
+  }
+#if !defined(TEST_VARIANT_HAS_NO_REFERENCES)
+  { // single argument - lvalue reference
+    using V = std::variant<int&>;
+    int x   = 42;
+    V v(x);
+    const V& cv = v;
+
+    v.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    cv.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(v).visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(cv).visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+  }
+  { // single argument - rvalue reference
+    using V = std::variant<int&&>;
+    int x   = 42;
+    V v(std::move(x));
+    const V& cv = v;
+
+    v.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    cv.visit<ReturnType>(obj);
+    assert(Fn::check_call<int&>(val));
+    std::move(v).visit<ReturnType>(obj);
+    assert(Fn::check_call<int&&>(val));
+    std::move(cv).visit<ReturnType>(obj);
+    assert(Fn::check_call<int&&>(val));
+  }
+#endif
+}
+
+template <typename ReturnType>
+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<decltype(std::visit<ReturnType>(obj)), ReturnType>);
+      static_assert(std::is_same_v<decltype(std::visit<ReturnType>(cobj)), ReturnType>);
+      static_assert(std::is_same_v<decltype(std::visit<ReturnType>(std::move(obj))), ReturnType>);
+      static_assert(std::is_same_v<decltype(std::visit<ReturnType>(std::move(cobj))), ReturnType>);
+    }
+  }
+  { // test call operator forwarding - single variant, single arg
+    using V = std::variant<int>;
+    V v(42);
+
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(obj)), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(cobj)), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(std::move(obj))), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(std::move(cobj))), ReturnType>);
+  }
+  { // test call operator forwarding - single variant, multi arg
+    using V = std::variant<int, long, double>;
+    V v(42L);
+
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(obj)), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(cobj)), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(std::move(obj))), ReturnType>);
+    static_assert(std::is_same_v<decltype(v.visit<ReturnType>(std::move(cobj))), ReturnType>);
+  }
+}
+
+void test_constexpr_void() {
+  constexpr ReturnFirst obj{};
+
+  {
+    using V = std::variant<int>;
+    constexpr V v(42);
+
+    static_assert((v.visit<void>(obj), 42) == 42);
+  }
+  {
+    using V = std::variant<short, long, char>;
+    constexpr V v(42L);
+
+    static_assert((v.visit<void>(obj), 42) == 42);
+  }
+}
+
+void test_constexpr_int() {
+  constexpr ReturnFirst obj{};
+
+  {
+    using V = std::variant<int>;
+    constexpr V v(42);
+
+    static_assert(v.visit<int>(obj) == 42);
+  }
+  {
+    using V = std::variant<short, long, char>;
+    constexpr V v(42L);
+
+    static_assert(v.visit<int>(obj) == 42);
+  }
+}
+
+template <typename ReturnType>
+void test_exceptions() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  ReturnArity obj{};
+
+  auto test = [&](auto&& v) {
+    try {
+      v.template visit<ReturnType>(obj);
+    } catch (const std::bad_variant_access&) {
+      return true;
+    } catch (...) {
+    }
+    return false;
+  };
+
+  {
+    using V = std::variant<int, MakeEmptyT>;
+    V v;
+    makeEmpty(v);
+
+    assert(test(v));
+  }
+#endif
+}
+
+// See https://bugs.llvm.org/show_bug.cgi?id=31916
+template <typename ReturnType>
+void test_caller_accepts_nonconst() {
+  struct A {};
+  struct Visitor {
+    auto operator()(A&) {
+      if constexpr (!std::is_void_v<ReturnType>) {
+        return ReturnType{};
+      }
+    }
+  };
+  std::variant<A> v;
+
+  v.template visit<ReturnType>(Visitor{});
+}
+
+void test_constexpr_explicit_side_effect() {
+  auto test_lambda = [](int arg) constexpr {
+    std::variant<int> v = 101;
+
+    {
+      v.template visit<void>([arg](int& x) constexpr { x = arg; });
+    }
+
+    return std::get<int>(v);
+  };
+
+  static_assert(test_lambda(202) == 202);
+}
+
+void test_derived_from_variant() {
+  struct MyVariant : std::variant<short, long, float> {};
+
+  MyVariant{42}.template visit<bool>([](auto x) {
+    assert(x == 42);
+    return true;
+  });
+  MyVariant{-1.3f}.template visit<bool>([](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<int, long, double>, std::tuple<int>, EvilVariantBase {
+    using std::variant<int, long, double>::variant;
+  };
+
+  EvilVariant1{12}.template visit<bool>([](auto x) {
+    assert(x == 12);
+    return true;
+  });
+  EvilVariant1{12.3}.template visit<bool>([](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<int, long, double>, ImplVariantBase {
+    using std::variant<int, long, double>::variant;
+  };
+
+  EvilVariant2{12}.template visit<bool>([](auto x) {
+    assert(x == 12);
+    return true;
+  });
+  EvilVariant2{12.3}.template visit<bool>([](auto x) {
+    assert(x == 12.3);
+    return true;
+  });
+}
+
+int main(int, char**) {
+  test_overload_ambiguity();
+  test_call_operator_forwarding<void>();
+  test_argument_forwarding<void>();
+  test_return_type<void>();
+  test_constexpr_void();
+  test_exceptions<void>();
+  test_caller_accepts_nonconst<void>();
+  test_call_operator_forwarding<int>();
+  test_argument_forwarding<int>();
+  test_return_type<int>();
+  test_constexpr_int();
+  test_exceptions<int>();
+  test_caller_accepts_nonconst<int>();
+  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 958c34edde0059..a5df187b046f66 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"],
         },
         {


        


More information about the libcxx-commits mailing list