diff --git a/include/boost/variant/variant.hpp b/include/boost/variant/variant.hpp index 33bdcb79..3c29892c 100644 --- a/include/boost/variant/variant.hpp +++ b/include/boost/variant/variant.hpp @@ -1033,13 +1033,15 @@ class invoke_visitor template result_type internal_visit(boost::detail::reference_content& operand, long) { - return internal_visit( operand.get(), 1L ); + invoke_visitor invoker(visitor_); + return invoker.internal_visit( operand.get(), 1L ); } template result_type internal_visit(const boost::detail::reference_content& operand, long) { - return internal_visit( operand.get(), 1L ); + invoke_visitor invoker(visitor_); + return invoker.internal_visit( operand.get(), 1L ); } template diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 37fa5a08..8fac81e9 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -36,6 +36,7 @@ test-suite variant [ run test9.cpp : : : /boost/fusion//boost_fusion : variant_test9 ] [ run recursive_variant_test.cpp ] [ run variant_reference_test.cpp ] + [ run variant_reference_visit_test.cpp ] [ run variant_comparison_test.cpp ] [ run variant_visit_internal_linkage.cpp : : : "$(below_cpp14)"\:no ] [ run variant_visit_test.cpp ] diff --git a/test/variant_reference_visit_test.cpp b/test/variant_reference_visit_test.cpp new file mode 100644 index 00000000..e61ebcb9 --- /dev/null +++ b/test/variant_reference_visit_test.cpp @@ -0,0 +1,185 @@ +// Copyright 2026 Treston Cuzzort +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or https://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include + +template +int category(T&&) +{ + return !std::is_lvalue_reference::value + + 2 * std::is_const::type>::value; +} + +struct category_visitor : boost::static_visitor +{ + template int operator()(T&& v) const + { return category(std::forward(v)); } + template int operator()(T&& a, U&& b) const + { return category(std::forward(a)) + 4 * category(std::forward(b)); } + template int operator()(T&& a, U&& b, V&& c) const + { return category(std::forward(a)) + 4 * category(std::forward(b)) + + 16 * category(std::forward(c)); } +}; + +#ifdef BOOST_VARIANT_HAS_DECLTYPE_APPLY_VISITOR_RETURN_TYPE +struct deduced_visitor +{ + template int operator()(T&& v) const + { return category(std::forward(v)); } + template int operator()(T&& a, U&& b) const + { return category(std::forward(a)) + 4 * category(std::forward(b)); } +}; + +struct deduced_reference +{ + template T& operator()(T& v) const { return v; } +}; +#endif + +template +void check_categories(Variant& v, int lvalue, int rvalue) +{ + category_visitor visitor; + BOOST_TEST_EQ(v.apply_visitor(visitor), lvalue); + BOOST_TEST_EQ(std::move(v).apply_visitor(visitor), rvalue); + BOOST_TEST_EQ(boost::apply_visitor(visitor, v), lvalue); + BOOST_TEST_EQ(boost::apply_visitor(visitor, std::move(v)), rvalue); +#ifdef BOOST_VARIANT_HAS_DECLTYPE_APPLY_VISITOR_RETURN_TYPE + deduced_visitor deduced; + BOOST_TEST_EQ(boost::apply_visitor(deduced, v), lvalue); + BOOST_TEST_EQ(boost::apply_visitor(deduced, std::move(v)), rvalue); +#endif +} + +struct changer : boost::static_visitor +{ + changer() {} + changer(const changer&) = delete; + changer& operator=(const changer&) = delete; + void operator()(int& i) { i += 10; } + void operator()(double& d) { d += 20; } +}; + +struct tracked +{ + static int copies, moves; + int value; + explicit tracked(int v) : value(v) {} + tracked(const tracked& v) : value(v.value) { ++copies; } + tracked(tracked&& v) : value(v.value) { ++moves; v.value = -1; } +}; +int tracked::copies = 0; +int tracked::moves = 0; + +struct consume : boost::static_visitor +{ + int operator()(tracked value) const { return value.value; } +}; + +struct address : boost::static_visitor +{ + tracked* operator()(tracked& value) const { return &value; } +}; + +void test_categories() +{ + int i = 1; + const int ci = 2; + boost::variant owned(i); + const boost::variant const_owned(i); + boost::variant borrowed(i); + const boost::variant const_borrowed(i); + boost::variant borrowed_const(ci); + const boost::variant const_borrowed_const(ci); + check_categories(owned, 0, 1); + check_categories(const_owned, 2, 3); + check_categories(borrowed, 0, 0); + check_categories(const_borrowed, 0, 0); + check_categories(borrowed_const, 2, 2); + check_categories(const_borrowed_const, 2, 2); + + category_visitor visitor; + BOOST_TEST_EQ(boost::apply_visitor(visitor, std::move(borrowed), std::move(owned)), 4); + BOOST_TEST_EQ(boost::apply_visitor(visitor, std::move(owned), std::move(borrowed)), 1); + BOOST_TEST_EQ(boost::apply_visitor(visitor, std::move(const_borrowed), std::move(borrowed_const)), 8); + BOOST_TEST_EQ(boost::apply_visitor(visitor, std::move(borrowed), std::move(owned), std::move(borrowed_const)), 36); +#ifdef BOOST_VARIANT_HAS_DECLTYPE_APPLY_VISITOR_RETURN_TYPE + deduced_visitor deduced; + BOOST_TEST_EQ(boost::apply_visitor(deduced, std::move(borrowed), std::move(owned)), 4); + BOOST_TEST_EQ(boost::apply_visitor(deduced, std::move(owned), std::move(borrowed)), 1); + deduced_reference reference; + static_assert(std::is_same::value, + "A borrowed alternative keeps its reference result"); + BOOST_TEST(&boost::apply_visitor(reference, std::move(borrowed)) == &i); + BOOST_TEST(&boost::apply_visitor(reference, std::move(const_borrowed)) == &i); +#endif +} + +void test_mutation_and_moves() +{ + int i = 0; + double d = 0; + typedef boost::variant references; + references v(i); + const references cv(i); + changer change; + boost::apply_visitor(change, v); + boost::apply_visitor(change, references(d)); + std::move(cv).apply_visitor(change); + BOOST_TEST_EQ(i, 20); + BOOST_TEST_EQ(d, 20); + + tracked external(42); + boost::variant borrowed(external); + const boost::variant const_borrowed(external); + consume sink; + tracked::copies = tracked::moves = 0; + BOOST_TEST_EQ(boost::apply_visitor(sink, std::move(borrowed)), 42); + BOOST_TEST_EQ(external.value, 42); + BOOST_TEST_EQ(tracked::copies, 1); + BOOST_TEST_EQ(tracked::moves, 0); + tracked::copies = tracked::moves = 0; + BOOST_TEST_EQ(boost::apply_visitor(sink, std::move(const_borrowed)), 42); + BOOST_TEST_EQ(external.value, 42); + BOOST_TEST_EQ(tracked::copies, 1); + BOOST_TEST_EQ(tracked::moves, 0); + + boost::variant owned(tracked(31)); + tracked::copies = tracked::moves = 0; + BOOST_TEST_EQ(boost::apply_visitor(sink, std::move(owned)), 31); + BOOST_TEST_EQ(boost::get(owned).value, -1); + BOOST_TEST_EQ(tracked::copies, 0); + BOOST_TEST_EQ(tracked::moves, 1); +} + +void test_recursive_references() +{ + boost::recursive_wrapper wrapped(tracked(7)); + typedef boost::variant&> references; + references v(wrapped); + const references cv(wrapped); + address visitor; + BOOST_TEST(boost::apply_visitor(visitor, v) == &wrapped.get()); + BOOST_TEST(boost::apply_visitor(visitor, std::move(v)) == &wrapped.get()); + BOOST_TEST(boost::apply_visitor(visitor, cv) == &wrapped.get()); + BOOST_TEST(boost::apply_visitor(visitor, std::move(cv)) == &wrapped.get()); + check_categories(v, 0, 0); + check_categories(cv, 0, 0); + boost::variant&> const_reference(wrapped); + check_categories(const_reference, 2, 2); + boost::variant > owned(wrapped.get()); + check_categories(owned, 0, 1); +} + +int main() +{ + test_categories(); + test_mutation_and_moves(); + test_recursive_references(); + return boost::report_errors(); +}