Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f8c52dc
[libc++][variant] P2637R3 - Member visit
Zingam Dec 18, 2023
c216ed8
Merge branch 'main' into task/hgh/P2637R3-member-visit
Zingam Dec 21, 2023
1310b54
Merge branch 'main' into task/hgh/P2637R3-member-visit
Zingam Dec 21, 2023
68075dd
WIP: Test via macro
Zingam Dec 24, 2023
0bc0832
WIP: visit.pass
Zingam Dec 24, 2023
1de898e
template_argument
Zingam Dec 24, 2023
a52349b
Implemented missing `visit` constraint + test
Zingam Dec 25, 2023
9b7064d
WIP: Updated visit tests
Zingam Dec 27, 2023
2d47529
WIP: tests
Zingam Dec 27, 2023
55516cd
WIP: more test refactoring
Zingam Dec 27, 2023
5097751
Completed: visit_return_type.pass
Zingam Dec 27, 2023
653ec7a
Syn cleanup
Zingam Dec 27, 2023
9126f03
Fixed formatting
Zingam Dec 27, 2023
ae8a8d6
Restored original files
Zingam Dec 30, 2023
4d3ff8b
Renamed files and cleanup
Zingam Dec 30, 2023
78f5d3e
Fixed: member.visit_return_type.pass.cpp
Zingam Dec 30, 2023
1bcbb51
Fixed: member.visit.pass.cpp
Zingam Dec 30, 2023
5df0fea
Formatted: visit.return_type.pass.cpp and visit.robust_against_adl.pa…
Zingam Dec 30, 2023
8eaaa41
Addressed comments
Zingam Dec 31, 2023
8f43a47
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
Zingam Dec 31, 2023
38e8f43
Removed test case
Zingam Dec 31, 2023
6de98f3
Addressed comment
Zingam Dec 31, 2023
759e0d5
WIP: Experiment with forward declaration
Zingam Dec 31, 2023
0a99bd9
Used forward declaration: `std::visit`
Zingam Dec 31, 2023
ed7bc12
Moved member `visit` to a separate folder.
Zingam Jan 1, 2024
ec32eb5
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
Zingam Jan 1, 2024
fde8ea9
Applied frederick-vs-ja's suggestion
Zingam Jan 3, 2024
47b09d3
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
Zingam Jan 17, 2024
989203e
Try to fix CI
Zingam Jan 17, 2024
668f337
Try to fix CI
Zingam Jan 17, 2024
06a2402
Fix CI on AIX and Arm
Zingam Jan 18, 2024
3d760da
Try to fix CI
Zingam Jan 18, 2024
b2ced9e
Try to fix CI on AIX
Zingam Jan 18, 2024
9c52e75
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
H-G-Hristov Jan 18, 2024
059b150
Addressed comments
Zingam Jan 18, 2024
376657c
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
H-G-Hristov Jan 18, 2024
c0dc054
Defined `_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER`
Zingam Jan 20, 2024
3bff454
Unsupport tests on *apple-clang*
Zingam Jan 20, 2024
edcdc37
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
Zingam Jan 20, 2024
8376b45
Fix the mistake
Zingam Jan 20, 2024
e68fe51
Fixed formatting
Zingam Jan 20, 2024
c056e69
Merge branch 'main' into hgh/libcxx/P2637R3-member-visit-variant
H-G-Hristov Jan 20, 2024
fa2cb78
Addressed comments
Zingam Jan 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libcxx/docs/Status/Cxx2c.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Paper Status
.. note::

.. [#note-P2510R3] This paper is applied as DR against C++20. (MSVC STL and libstdc++ will do the same.)
.. [#note-P2637R3] P2637R3: Implemented `variant` member `visit`


.. _issues-status-cxx2c:

Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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| [#note-P2637R3]","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","","",""
"`P2697R1 <https://wg21.link/P2697R1>`__","LWG","Interfacing ``bitset`` with ``string_view``","Varna June 2023","|Complete|","18.0",""
Expand Down
34 changes: 34 additions & 0 deletions libcxx/include/variant
Original file line number Diff line number Diff line change
Expand Up @@ -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&&);
template<class R, class Self, class Visitor>
constexpr R visit(this Self&&, Visitor&&);
};

// 20.7.3, variant helper classes
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -1273,6 +1280,15 @@ public:
__impl_.__swap(__that.__impl_);
}

# if _LIBCPP_STD_VER >= 26
// [variant.visit], visitation
template <int=0, class _Self, class _Visitor>
constexpr decltype(auto) visit(this _Self&& __self, _Visitor&& __visitor);

template <class _R, class _Self, class _Visitor>
constexpr _R visit(this _Self&& __self, _Visitor&& __visitor);
# endif

private:
__variant_detail::__impl<_Types...> __impl_;

Expand Down Expand Up @@ -1532,6 +1548,24 @@ visit(_Visitor&& __visitor, _Vs&&... __vs) {
}
# endif

# if _LIBCPP_STD_VER >= 26
// [variant.visit], visitation

template <class... _Types>
template <int, class _Self, class _Visitor>
constexpr decltype(auto) variant<_Types...>::visit(this _Self&& __self, _Visitor&& __visitor) {
using _V = _OverrideRef<_Self&&, _CopyConst<remove_reference_t<_Self>, variant>>;
return std::visit(std::forward<_Visitor>(__visitor), (_V)__self);
}

template <class... _Types>
template <class _Rp, class _Self, class _Visitor>
constexpr _Rp variant<_Types...>::visit(this _Self&& __self, _Visitor&& __visitor) {
using _V = _OverrideRef<_Self&&, _CopyConst<remove_reference_t<_Self>, variant>>;
return std::visit<_Rp>(std::forward<_Visitor>(__visitor), (_V)__self);
}
# endif

template <class... _Types>
_LIBCPP_HIDE_FROM_ABI auto
swap(variant<_Types...>& __lhs, variant<_Types...>& __rhs) noexcept(noexcept(__lhs.swap(__rhs)))
Expand Down
268 changes: 268 additions & 0 deletions libcxx/test/std/utilities/variant/variant.visit/member.visit.pass.cpp
Original file line number Diff line number Diff line change
@@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you not using our normal constexpr test methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

test_exceptions();
test_caller_accepts_nonconst();
test_derived_from_variant();

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
//
// 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
// 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);
#if TEST_STD_VER > 17
static_assert(test(true));
#endif
return 0;
}
Loading