[flang] [libcxx] [lldb] [clang] [compiler-rt] [libc] [clang-tools-extra] [llvm] [libc++][variant] P2637R3: Member `visit` (`std::variant`) (PR #76447)

Hristo Hristov via cfe-commits cfe-commits at lists.llvm.org
Sun Dec 31 00:39:30 PST 2023


================
@@ -0,0 +1,268 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <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 - no variant
+    // non-member
+    {
+      std::visit(obj);
+      assert(Fn::check_call<>(CT_NonConst | CT_LValue));
+      std::visit(cobj);
+      assert(Fn::check_call<>(CT_Const | CT_LValue));
+      std::visit(std::move(obj));
+      assert(Fn::check_call<>(CT_NonConst | CT_RValue));
+      std::visit(std::move(cobj));
+      assert(Fn::check_call<>(CT_Const | CT_RValue));
+    }
+  }
+  { // 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();
----------------
H-G-Hristov wrote:

My reasoning was to keep the member visit tests as close as possible to the original non-member function ones, for easier matching/reviewing as they are expected to produce the same results. I kept the structure and the names. Initially I wrote the tests in the same file before splitting them into two separate.

https://github.com/llvm/llvm-project/pull/76447


More information about the cfe-commits mailing list