diff --git a/libcxx/include/variant b/libcxx/include/variant --- a/libcxx/include/variant +++ b/libcxx/include/variant @@ -169,6 +169,9 @@ template constexpr see below visit(Visitor&&, Variants&&...); + template + constexpr R visit(Visitor&&, Variants&&...); + // 20.7.7, class monostate struct monostate; @@ -584,6 +587,15 @@ _VSTD::forward<_Vs>(__vs)...); } + template + inline _LIBCPP_INLINE_VISIBILITY + static constexpr _Rp __visit_value(_Visitor&& __visitor, + _Vs&&... __vs) { + return __visit_alt( + __make_value_visitor<_Rp>(_VSTD::forward<_Visitor>(__visitor)), + _VSTD::forward<_Vs>(__vs)...); + } + private: template static constexpr void __std_visit_exhaustive_visitor_check() { @@ -605,11 +617,38 @@ _Visitor&& __visitor; }; + template + struct __value_visitor_return_type { + template + inline _LIBCPP_INLINE_VISIBILITY + constexpr _Rp operator()(_Alts&&... __alts) const { + __std_visit_exhaustive_visitor_check< + _Visitor, + decltype((_VSTD::forward<_Alts>(__alts).__value))...>(); + if constexpr (is_void_v<_Rp>) { + __invoke_constexpr(_VSTD::forward<_Visitor>(__visitor), + _VSTD::forward<_Alts>(__alts).__value...); + } + else { + return __invoke_constexpr(_VSTD::forward<_Visitor>(__visitor), + _VSTD::forward<_Alts>(__alts).__value...); + } + } + + _Visitor&& __visitor; + }; + template inline _LIBCPP_INLINE_VISIBILITY static constexpr auto __make_value_visitor(_Visitor&& __visitor) { return __value_visitor<_Visitor>{_VSTD::forward<_Visitor>(__visitor)}; } + + template + inline _LIBCPP_INLINE_VISIBILITY + static constexpr auto __make_value_visitor(_Visitor&& __visitor) { + return __value_visitor_return_type<_Rp, _Visitor>{_VSTD::forward<_Visitor>(__visitor)}; + } }; } // namespace __visitation @@ -1594,18 +1633,35 @@ template inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS -constexpr decltype(auto) visit(_Visitor&& __visitor, _Vs&&... __vs) { - using __variant_detail::__visitation::__variant; - bool __results[] = {__vs.valueless_by_exception()...}; - for (bool __result : __results) { - if (__result) { +constexpr void __throw_if_valueless(_Visitor&& __visitor, _Vs&&... __vs) { + const bool __valueless = (... || __vs.valueless_by_exception()); + if (__valueless) { __throw_bad_variant_access(); - } } +} + +template +inline _LIBCPP_INLINE_VISIBILITY +_LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS +constexpr decltype(auto) visit(_Visitor&& __visitor, _Vs&&... __vs) { + using __variant_detail::__visitation::__variant; + __throw_if_valueless(_VSTD::forward<_Visitor>(__visitor), + _VSTD::forward<_Vs>(__vs)...); return __variant::__visit_value(_VSTD::forward<_Visitor>(__visitor), _VSTD::forward<_Vs>(__vs)...); } +template +inline _LIBCPP_INLINE_VISIBILITY +_LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS +constexpr _Rp visit(_Visitor&& __visitor, _Vs&&... __vs) { + using __variant_detail::__visitation::__variant; + __throw_if_valueless(_VSTD::forward<_Visitor>(__visitor), + _VSTD::forward<_Vs>(__vs)...); + return __variant::__visit_value<_Rp>(_VSTD::forward<_Visitor>(__visitor), + _VSTD::forward<_Vs>(__vs)...); +} + struct _LIBCPP_TEMPLATE_VIS monostate {}; inline _LIBCPP_INLINE_VISIBILITY diff --git a/libcxx/test/std/utilities/variant/variant.visit/visit.pass.cpp b/libcxx/test/std/utilities/variant/variant.visit/visit.pass.cpp --- a/libcxx/test/std/utilities/variant/variant.visit/visit.pass.cpp +++ b/libcxx/test/std/utilities/variant/variant.visit/visit.pass.cpp @@ -27,70 +27,8 @@ #include #include "test_macros.h" -#include "type_id.h" #include "variant_test_helpers.h" -enum CallType : unsigned { - CT_None, - CT_NonConst = 1, - CT_Const = 2, - CT_LValue = 4, - CT_RValue = 8 -}; - -inline constexpr CallType operator|(CallType LHS, CallType RHS) { - return static_cast(static_cast(LHS) | - static_cast(RHS)); -} - -struct ForwardingCallObject { - - template - ForwardingCallObject& operator()(Args&&...) & { - set_call(CT_NonConst | CT_LValue); - return *this; - } - - template - const ForwardingCallObject& operator()(Args&&...) const & { - set_call(CT_Const | CT_LValue); - return *this; - } - - template - ForwardingCallObject&& operator()(Args&&...) && { - set_call(CT_NonConst | CT_RValue); - return std::move(*this); - } - - template - const ForwardingCallObject&& operator()(Args&&...) const && { - set_call(CT_Const | CT_RValue); - return std::move(*this); - } - - template static void set_call(CallType type) { - assert(last_call_type == CT_None); - assert(last_call_args == nullptr); - last_call_type = type; - last_call_args = std::addressof(makeArgumentID()); - } - - template static bool check_call(CallType type) { - bool result = last_call_type == type && last_call_args && - *last_call_args == makeArgumentID(); - last_call_type = CT_None; - last_call_args = nullptr; - return result; - } - - static CallType last_call_type; - static const TypeID *last_call_args; -}; - -CallType ForwardingCallObject::last_call_type = CT_None; -const TypeID *ForwardingCallObject::last_call_args = nullptr; - void test_call_operator_forwarding() { using Fn = ForwardingCallObject; Fn obj{}; @@ -296,18 +234,6 @@ } } -struct ReturnFirst { - template constexpr int operator()(int f, Args &&...) const { - return f; - } -}; - -struct ReturnArity { - template constexpr int operator()(Args &&...) const { - return sizeof...(Args); - } -}; - void test_constexpr() { constexpr ReturnFirst obj{}; constexpr ReturnArity aobj{}; @@ -412,23 +338,13 @@ #endif } -// See https://bugs.llvm.org/show_bug.cgi?id=31916 -void test_caller_accepts_nonconst() { - struct A {}; - struct Visitor { - void operator()(A&) {} - }; - std::variant v; - std::visit(Visitor{}, v); -} - int main(int, char**) { test_call_operator_forwarding(); test_argument_forwarding(); test_return_type(); test_constexpr(); test_exceptions(); - test_caller_accepts_nonconst(); + test_caller_accepts_nonconst(); return 0; } diff --git a/libcxx/test/std/utilities/variant/variant.visit/visit_return_type.pass.cpp b/libcxx/test/std/utilities/variant/variant.visit/visit_return_type.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/variant/variant.visit/visit_return_type.pass.cpp @@ -0,0 +1,403 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 + +// Throwing bad_variant_access is supported starting in macosx10.13 +// XFAIL: with_system_cxx_lib=macosx10.12 && !no-exceptions +// XFAIL: with_system_cxx_lib=macosx10.11 && !no-exceptions +// XFAIL: with_system_cxx_lib=macosx10.10 && !no-exceptions +// XFAIL: with_system_cxx_lib=macosx10.9 && !no-exceptions + +// +// template +// constexpr R visit(Visitor&& vis, Variants&&... vars); + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "variant_test_helpers.h" + +template +void test_call_operator_forwarding() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn &cobj = obj; + { // test call operator forwarding - no variant + 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; + V v(42); + std::visit(obj, v); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + std::visit(cobj, v); + assert(Fn::check_call(CT_Const | CT_LValue)); + std::visit(std::move(obj), v); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + std::visit(std::move(cobj), v); + assert(Fn::check_call(CT_Const | CT_RValue)); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42l); + std::visit(obj, v); + assert(Fn::check_call(CT_NonConst | CT_LValue)); + std::visit(cobj, v); + assert(Fn::check_call(CT_Const | CT_LValue)); + std::visit(std::move(obj), v); + assert(Fn::check_call(CT_NonConst | CT_RValue)); + std::visit(std::move(cobj), v); + assert(Fn::check_call(CT_Const | CT_RValue)); + } + { // test call operator forwarding - multi variant, multi arg + using V = std::variant; + using V2 = std::variant; + V v(42l); + V2 v2("hello"); + std::visit(obj, v, v2); + assert((Fn::check_call(CT_NonConst | CT_LValue))); + std::visit(cobj, v, v2); + assert((Fn::check_call(CT_Const | CT_LValue))); + std::visit(std::move(obj), v, v2); + assert((Fn::check_call(CT_NonConst | CT_RValue))); + std::visit(std::move(cobj), v, v2); + assert((Fn::check_call(CT_Const | CT_RValue))); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(101), v4(1.1); + std::visit(obj, v1, v2, v3, v4); + assert((Fn::check_call(CT_NonConst | CT_LValue))); + std::visit(cobj, v1, v2, v3, v4); + assert((Fn::check_call(CT_Const | CT_LValue))); + std::visit(std::move(obj), v1, v2, v3, v4); + assert((Fn::check_call(CT_NonConst | CT_RValue))); + std::visit(std::move(cobj), v1, v2, v3, v4); + assert((Fn::check_call(CT_Const | CT_RValue))); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(nullptr), v4(1.1); + std::visit(obj, v1, v2, v3, v4); + assert((Fn::check_call(CT_NonConst | CT_LValue))); + std::visit(cobj, v1, v2, v3, v4); + assert((Fn::check_call(CT_Const | CT_LValue))); + std::visit(std::move(obj), v1, v2, v3, v4); + assert((Fn::check_call(CT_NonConst | CT_RValue))); + std::visit(std::move(cobj), v1, v2, v3, v4); + 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; + std::visit(obj, v); + assert(Fn::check_call(Val)); + std::visit(obj, cv); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(v)); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(cv)); + 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; + std::visit(obj, v); + assert(Fn::check_call(Val)); + std::visit(obj, cv); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(v)); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(cv)); + 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; + std::visit(obj, v); + assert(Fn::check_call(Val)); + std::visit(obj, cv); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(v)); + assert(Fn::check_call(Val)); + std::visit(obj, std::move(cv)); + assert(Fn::check_call(Val)); + } +#endif + { // multi argument - multi variant + using V = std::variant; + V v1(42), v2("hello"), v3(43l); + std::visit(obj, v1, v2, v3); + assert((Fn::check_call(Val))); + std::visit(obj, std::as_const(v1), std::as_const(v2), std::move(v3)); + assert((Fn::check_call(Val))); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(101), v4(1.1); + std::visit(obj, v1, v2, v3, v4); + assert((Fn::check_call(Val))); + std::visit(obj, std::as_const(v1), std::as_const(v2), std::move(v3), std::move(v4)); + assert((Fn::check_call(Val))); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(nullptr), v4(1.1); + std::visit(obj, v1, v2, v3, v4); + assert((Fn::check_call(Val))); + std::visit(obj, std::as_const(v1), std::as_const(v2), std::move(v3), std::move(v4)); + assert((Fn::check_call(Val))); + } +} + +template +void test_return_type() { + using Fn = ForwardingCallObject; + Fn obj{}; + const Fn &cobj = obj; + { // test call operator forwarding - no variant + 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, v)), ReturnType>); + static_assert(std::is_same_v(cobj, v)), ReturnType>); + static_assert(std::is_same_v(std::move(obj), v)), ReturnType>); + static_assert(std::is_same_v(std::move(cobj), v)), ReturnType>); + } + { // test call operator forwarding - single variant, multi arg + using V = std::variant; + V v(42l); + static_assert(std::is_same_v(obj, v)), ReturnType>); + static_assert(std::is_same_v(cobj, v)), ReturnType>); + static_assert(std::is_same_v(std::move(obj), v)), ReturnType>); + static_assert(std::is_same_v(std::move(cobj), v)), ReturnType>); + } + { // test call operator forwarding - multi variant, multi arg + using V = std::variant; + using V2 = std::variant; + V v(42l); + V2 v2("hello"); + static_assert(std::is_same_v(obj, v, v2)), ReturnType>); + static_assert(std::is_same_v(cobj, v, v2)), ReturnType>); + static_assert(std::is_same_v(std::move(obj), v, v2)), ReturnType>); + static_assert(std::is_same_v(std::move(cobj), v, v2)), ReturnType>); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(101), v4(1.1); + static_assert(std::is_same_v(obj, v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(cobj, v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(std::move(obj), v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(std::move(cobj), v1, v2, v3, v4)), ReturnType>); + } + { + using V = std::variant; + V v1(42l), v2("hello"), v3(nullptr), v4(1.1); + static_assert(std::is_same_v(obj, v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(cobj, v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(std::move(obj), v1, v2, v3, v4)), ReturnType>); + static_assert(std::is_same_v(std::move(cobj), v1, v2, v3, v4)), ReturnType>); + } +} + +void test_constexpr_void() { + constexpr ReturnFirst obj{}; + constexpr ReturnArity aobj{}; + { + using V = std::variant; + constexpr V v(42); + static_assert((std::visit(obj, v), 42) == 42, ""); + } + { + using V = std::variant; + constexpr V v(42l); + static_assert((std::visit(obj, v), 42) == 42, ""); + } + { + using V1 = std::variant; + using V2 = std::variant; + using V3 = std::variant; + constexpr V1 v1; + constexpr V2 v2(nullptr); + constexpr V3 v3; + static_assert((std::visit(aobj, v1, v2, v3), 3) == 3, ""); + } + { + using V1 = std::variant; + using V2 = std::variant; + using V3 = std::variant; + constexpr V1 v1; + constexpr V2 v2(nullptr); + constexpr V3 v3; + static_assert((std::visit(aobj, v1, v2, v3), 3) == 3, ""); + } + { + using V = std::variant; + constexpr V v1(42l), v2(101), v3(nullptr), v4(1.1); + static_assert((std::visit(aobj, v1, v2, v3, v4), 4) == 4, ""); + } + { + using V = std::variant; + constexpr V v1(42l), v2(101), v3(nullptr), v4(1.1); + static_assert((std::visit(aobj, v1, v2, v3, v4), 4) == 4, ""); + } +} + +void test_constexpr_int() { + constexpr ReturnFirst obj{}; + constexpr ReturnArity aobj{}; + { + using V = std::variant; + constexpr V v(42); + static_assert(std::visit(obj, v) == 42, ""); + } + { + using V = std::variant; + constexpr V v(42l); + static_assert(std::visit(obj, v) == 42, ""); + } + { + using V1 = std::variant; + using V2 = std::variant; + using V3 = std::variant; + constexpr V1 v1; + constexpr V2 v2(nullptr); + constexpr V3 v3; + static_assert(std::visit(aobj, v1, v2, v3) == 3, ""); + } + { + using V1 = std::variant; + using V2 = std::variant; + using V3 = std::variant; + constexpr V1 v1; + constexpr V2 v2(nullptr); + constexpr V3 v3; + static_assert(std::visit(aobj, v1, v2, v3) == 3, ""); + } + { + using V = std::variant; + constexpr V v1(42l), v2(101), v3(nullptr), v4(1.1); + static_assert(std::visit(aobj, v1, v2, v3, v4) == 4, ""); + } + { + using V = std::variant; + constexpr V v1(42l), v2(101), v3(nullptr), v4(1.1); + static_assert(std::visit(aobj, v1, v2, v3, v4) == 4, ""); + } +} + +template +void test_exceptions() { +#ifndef TEST_HAS_NO_EXCEPTIONS + ReturnArity obj{}; + auto test = [&](auto &&... args) { + try { + std::visit(obj, args...); + } catch (const std::bad_variant_access &) { + return true; + } catch (...) { + } + return false; + }; + { + using V = std::variant; + V v; + makeEmpty(v); + assert(test(v)); + } + { + using V = std::variant; + using V2 = std::variant; + V v; + makeEmpty(v); + V2 v2("hello"); + assert(test(v, v2)); + } + { + using V = std::variant; + using V2 = std::variant; + V v; + makeEmpty(v); + V2 v2("hello"); + assert(test(v2, v)); + } + { + using V = std::variant; + using V2 = std::variant; + V v; + makeEmpty(v); + V2 v2; + makeEmpty(v2); + assert(test(v, v2)); + } + { + using V = std::variant; + V v1(42l), v2(101), v3(202), v4(1.1); + makeEmpty(v1); + assert(test(v1, v2, v3, v4)); + } + { + using V = std::variant; + V v1(42l), v2(101), v3(202), v4(1.1); + makeEmpty(v1); + makeEmpty(v2); + makeEmpty(v3); + makeEmpty(v4); + assert(test(v1, v2, v3, v4)); + } +#endif +} + +int main(int, char**) { + 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(); + + return 0; +} diff --git a/libcxx/test/support/variant_test_helpers.h b/libcxx/test/support/variant_test_helpers.h --- a/libcxx/test/support/variant_test_helpers.h +++ b/libcxx/test/support/variant_test_helpers.h @@ -12,8 +12,10 @@ #include #include #include +#include #include "test_macros.h" +#include "type_id.h" #if TEST_STD_VER <= 14 #error This file requires C++17 @@ -85,5 +87,99 @@ } #endif // TEST_HAS_NO_EXCEPTIONS +enum CallType : unsigned { + CT_None, + CT_NonConst = 1, + CT_Const = 2, + CT_LValue = 4, + CT_RValue = 8 +}; + +inline constexpr CallType operator|(CallType LHS, CallType RHS) { + return static_cast(static_cast(LHS) | + static_cast(RHS)); +} + +struct ForwardingCallObject { + + template + ForwardingCallObject& operator()(Args&&...) & { + set_call(CT_NonConst | CT_LValue); + return *this; + } + + template + const ForwardingCallObject& operator()(Args&&...) const & { + set_call(CT_Const | CT_LValue); + return *this; + } + + template + ForwardingCallObject&& operator()(Args&&...) && { + set_call(CT_NonConst | CT_RValue); + return std::move(*this); + } + + template + const ForwardingCallObject&& operator()(Args&&...) const && { + set_call(CT_Const | CT_RValue); + return std::move(*this); + } + + template static void set_call(CallType type) { + assert(last_call_type == CT_None); + assert(last_call_args == nullptr); + last_call_type = type; + last_call_args = std::addressof(makeArgumentID()); + } + + template static bool check_call(CallType type) { + bool result = last_call_type == type && last_call_args && + *last_call_args == makeArgumentID(); + last_call_type = CT_None; + last_call_args = nullptr; + return result; + } + + // To check explicit return type for visit + constexpr operator int() const + { + return 0; + } + + static CallType last_call_type; + static const TypeID *last_call_args; +}; + +CallType ForwardingCallObject::last_call_type = CT_None; +const TypeID *ForwardingCallObject::last_call_args = nullptr; + +struct ReturnFirst { + template constexpr int operator()(int f, Args &&...) const { + return f; + } +}; + +struct ReturnArity { + template constexpr int operator()(Args &&...) const { + return sizeof...(Args); + } +}; + +// 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; + std::visit(Visitor{}, v); +} #endif // SUPPORT_VARIANT_TEST_HELPERS_H diff --git a/libcxx/www/cxx2a_status.html b/libcxx/www/cxx2a_status.html --- a/libcxx/www/cxx2a_status.html +++ b/libcxx/www/cxx2a_status.html @@ -116,7 +116,7 @@ P0595R2CWGP0595R2 std::is_constant_evaluated()San DiegoComplete9.0 P0602R4LWGvariant and optional should propagate copy/move trivialitySan DiegoComplete8.0 P0608R3LWGA sane variant converting constructorSan DiegoComplete9.0 - P0655R1LWGvisit<R>: Explicit Return Type for visitSan Diego + P0655R1LWGvisit<R>: Explicit Return Type for visitSan DiegoComplete12.0 P0771R1LWGstd::function move constructor should be noexceptSan DiegoComplete6.0 P0896R4LWGThe One Ranges ProposalSan Diego P0899R1LWGP0899R1 - LWG 3016 is not a defectSan DiegoNothing to do