From bc3cec4634511112a05af734bdee6ebbe0e30845 Mon Sep 17 00:00:00 2001 From: koe Date: Thu, 1 Dec 2022 17:20:17 -0600 Subject: [PATCH 1/2] add variant class with cleaner interface than boost::variant<> --- src/common/CMakeLists.txt | 3 + src/common/variant.h | 156 ++++++++++++ tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/variant.cpp | 415 ++++++++++++++++++++++++++++++++ 4 files changed, 575 insertions(+) create mode 100644 src/common/variant.h create mode 100644 tests/unit_tests/variant.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index b712ee6b1..189207fad 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -81,6 +81,9 @@ target_link_libraries(common PRIVATE ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) +target_include_directories(common + PRIVATE + ${Boost_INCLUDE_DIRS}) #monero_install_headers(common # ${common_headers}) diff --git a/src/common/variant.h b/src/common/variant.h new file mode 100644 index 000000000..a2121c75e --- /dev/null +++ b/src/common/variant.h @@ -0,0 +1,156 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Variant wrapper class. + +#pragma once + +//local headers + +//third party headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//standard headers +#include +#include +#include + +//forward declarations + + +namespace tools +{ + +[[noreturn]] inline void variant_static_visitor_blank_err() +{ throw std::runtime_error("variant: tried to visit an empty variant."); } +[[noreturn]] inline void variant_unwrap_err() +{ throw std::runtime_error("variant: tried to access value of incorrect type."); } + +//// +// variant: convenience wrapper around boost::variant with a cleaner interface +// - the value may be assigned to but is otherwise read-only +// - the variant is 'optional' - an empty variant will evaluate to 'false' and an initialized variant will be 'true' +/// +template +struct variant_static_visitor : public boost::static_visitor +{ + /// provide visitation for empty variants + /// - add this to your visitor with: using variant_static_visitor::operator(); + [[noreturn]] ResultT operator()(const boost::blank) const { variant_static_visitor_blank_err(); } +}; + +template +class variant final +{ + using VType = boost::variant; + +public: +//constructors + /// default constructor + variant() = default; + variant(boost::none_t) : variant{} {} //act like boost::optional + + /// construct from variant type (use enable_if to avoid issues with copy/move constructor) + template >, + variant + >::value, + bool + >::type = true> + variant(T &&value) : m_value{std::forward(value)} {} + +//overloaded operators + /// boolean operator: true if the variant isn't empty/uninitialized + explicit operator bool() const noexcept { return !this->is_empty(); } + +//member functions + /// check if empty/uninitialized + bool is_empty() const noexcept { return m_value.which() == 0; } + + /// check the variant type + template + bool is_type() const noexcept { return this->index() == this->type_index_of(); } + + /// try to get a read-only handle to the embedded value (return nullptr on failure) + template + const T* try_unwrap() const + { + return boost::strict_get(&m_value); + } + + /// get a read-only handle to the embedded value + template + const T& unwrap() const + { + const T *value_ptr{this->try_unwrap()}; + if (!value_ptr) variant_unwrap_err(); + return *value_ptr; + } + + /// get the type index of the currently stored type + int index() const noexcept { return m_value.which(); } + + /// get the type index of a requested type (compile error for invalid types) (boost::mp11 is boost 1.66.0) + template + static constexpr int type_index_of() noexcept + { + using types = boost::mpl::vector; + using elem = typename boost::mpl::find::type; + using begin = typename boost::mpl::begin::type; + return boost::mpl::distance::value; + } + + /// check if two variants have the same type + static bool same_type(const variant &v1, const variant &v2) noexcept + { return v1.index() == v2.index(); } + + /// apply a visitor to the variant + template + typename VisitorT::result_type visit(VisitorT &&visitor) const + { + return boost::apply_visitor(std::forward(visitor), m_value); + } + +private: +//member variables + /// variant of all value types + VType m_value; +}; + +} //namespace tools diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 55818dc93..835567953 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -90,6 +90,7 @@ set(unit_tests_sources hardfork.cpp unbound.cpp uri.cpp + variant.cpp varint.cpp ringct.cpp output_selection.cpp diff --git a/tests/unit_tests/variant.cpp b/tests/unit_tests/variant.cpp new file mode 100644 index 000000000..c22200a8c --- /dev/null +++ b/tests/unit_tests/variant.cpp @@ -0,0 +1,415 @@ +// Copyright (c) 2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "common/variant.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +#include +#include +#include + +using tools::variant; +using tools::variant_static_visitor; + +namespace +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +using strip_all_t = std::remove_reference_t>; +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +using strip_same = std::is_same, strip_all_t>; +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + typename PositiveType, + typename TestType, + typename... VariantTypes, + class VecTypes = boost::mpl::vector, + class VecBegin = typename boost::mpl::begin::type, + class VecIndexT = typename boost::mpl::find::type, + size_t TYPE_INDEX = boost::mpl::distance::value, + bool LAST_VARIANT_TYPE = TYPE_INDEX == sizeof...(VariantTypes) - 1 +> +static std::enable_if_t +test_is_type_match(const variant& v) +{ + constexpr bool expected = strip_same(); + const bool actual = v.template is_type(); + EXPECT_EQ(expected, actual); + + EXPECT_FALSE(v.template is_type()); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + typename PositiveType, + typename TestType, + typename... VariantTypes, + class VecTypes = boost::mpl::vector, + class VecBegin = typename boost::mpl::begin::type, + class VecIndexT = typename boost::mpl::find::type, + size_t TYPE_INDEX = boost::mpl::distance::value, + bool LAST_VARIANT_TYPE = TYPE_INDEX == sizeof...(VariantTypes) - 1 +> +static std::enable_if_t +test_is_type_match(const variant& v) +{ + constexpr bool expected = strip_same(); + const bool actual = v.template is_type(); + EXPECT_EQ(expected, actual); + + using NextTypeIt = typename boost::mpl::advance>::type; + using NextTestType = typename boost::mpl::deref::type; + test_is_type_match(v); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + typename VariantType0, + typename... VariantTypesRest, + typename AssignType +> +static void test_is_type_ref +( + variant& v, + AssignType&& val +) +{ + v = val; + test_is_type_match(v); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + typename VariantType0, + typename... VariantTypesRest, + typename AssignType0, + typename... AssignTypesRest +> +static void test_is_type_ref +( + variant& v, + AssignType0&& val_0, + AssignTypesRest&&... val_rest +) +{ + v = val_0; + test_is_type_match(v); + test_is_type_ref(v, val_rest...); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +static void test_is_type_full(VariantTypes&&... test_vals) +{ + variant v; + test_is_type_ref(v, test_vals...); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + size_t IJ = 0, + typename... VariantTypes, + bool END = IJ == sizeof...(VariantTypes) * sizeof...(VariantTypes) +> +static std::enable_if_t +test_same_type_ref +( + variant& v1, + variant& v2, + const std::tuple& tup_i, + const std::tuple& tup_j +) +{ /* trivial end case */ } +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +< + size_t IJ = 0, + typename... VariantTypes, + bool END = IJ == sizeof...(VariantTypes) * sizeof...(VariantTypes) +> +static std::enable_if_t +test_same_type_ref +( + variant& v1, + variant& v2, + const std::tuple& tup_i, + const std::tuple& tup_j +) +{ + constexpr size_t I = IJ / sizeof...(VariantTypes); + constexpr size_t J = IJ % sizeof...(VariantTypes); + constexpr bool expected = I == J; + + v1 = std::get(tup_i); + v2 = std::get(tup_j); + const bool actual = variant::same_type(v1, v2); + + EXPECT_EQ(expected, actual); + + test_same_type_ref(v1, v2, tup_i, tup_j); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +static void test_same_type_full +( + const std::tuple& vals_i, + const std::tuple& vals_j +) +{ + using Variant = variant; + Variant v_i; + Variant v_j; + test_same_type_ref(v_i, v_j, vals_i, vals_j); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +struct test_stringify_visitor: public variant_static_visitor +{ + template + static std::string stringify(const T& t) + { + std::stringstream ss; + ss << typeid(T).name(); + ss << "::"; + ss << t; + return ss.str(); + } + + template + static void test_visitation(const Variant& v, const T& t) + { + EXPECT_EQ(test_stringify_visitor::stringify(t), v.visit(test_stringify_visitor())); + } + + // Make sure boost::blank errors + using variant_static_visitor::operator(); + + // Visitation implementation + template + std::string operator()(const T& t) const + { + return test_stringify_visitor::stringify(t); + } +}; +} // anonymous namespace +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, operatorbool) +{ + variant v; + EXPECT_FALSE(v); + v = (int16_t) 2023; + EXPECT_TRUE(v); + v = (int16_t) 0; + EXPECT_TRUE(v); + v = boost::blank{}; + EXPECT_FALSE(v); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, is_empty) +{ + variant v; + EXPECT_TRUE(v.is_empty()); + v = (int16_t) 2023; + EXPECT_FALSE(v.is_empty()); + v = (int16_t) 0; + EXPECT_FALSE(v.is_empty()); + v = boost::blank{}; + EXPECT_TRUE(v.is_empty()); + + variant<> v2; + EXPECT_TRUE(v2.is_empty()); + v2 = boost::blank{}; + EXPECT_TRUE(v2.is_empty()); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, is_type) +{ + variant v; + EXPECT_TRUE(v.is_type()); + v = (int16_t) 2023; + EXPECT_TRUE(v.is_type()); + + test_is_type_full((uint32_t) 2023, (char) '\n', std::string("HOWDY")); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, try_unwrap) +{ + variant v; + EXPECT_FALSE(v.try_unwrap()); + v = (int16_t) 5252; + ASSERT_TRUE(v.try_unwrap()); + EXPECT_EQ(5252, *v.try_unwrap()); + EXPECT_FALSE(v.try_unwrap()); + EXPECT_FALSE(v.try_unwrap()); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, unwrap) +{ + variant v; + EXPECT_THROW(v.unwrap(), std::runtime_error); + v = (int16_t) 5252; + EXPECT_EQ(5252, v.unwrap()); + EXPECT_THROW(v.unwrap(), std::runtime_error); + EXPECT_THROW(v.unwrap(), std::runtime_error); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, index) +{ + variant v; + EXPECT_EQ(0, v.index()); + v = (int8_t) 7; + EXPECT_EQ(1, v.index()); + v = (uint8_t) 7; + EXPECT_EQ(2, v.index()); + v = (int16_t) 7; + EXPECT_EQ(3, v.index()); + v = (uint16_t) 7; + EXPECT_EQ(4, v.index()); + v = "verifiable variant vying for vengence versus visa"; + EXPECT_EQ(5, v.index()); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, type_index_of) +{ + variant v; + EXPECT_EQ(0, decltype(v)::type_index_of()); + EXPECT_EQ(1, decltype(v)::type_index_of()); + EXPECT_EQ(2, decltype(v)::type_index_of()); + EXPECT_EQ(3, decltype(v)::type_index_of()); + EXPECT_EQ(4, decltype(v)::type_index_of()); + EXPECT_EQ(5, decltype(v)::type_index_of()); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, constexpr_type_index_of) +{ + variant v; + constexpr int TINDEX0 = decltype(v)::type_index_of(); + EXPECT_EQ(0, TINDEX0); + constexpr int TINDEX5 = decltype(v)::type_index_of(); + EXPECT_EQ(5, TINDEX5); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, same_type) +{ + const std::tuple vals_i(77840, "Hullubaloo", '\0'); + const std::tuple vals_j(1876, "Canneck", '\t'); + test_same_type_full(vals_i, vals_j); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, visit) +{ + variant v; + EXPECT_THROW(v.visit(test_stringify_visitor()), std::runtime_error); + + v = "Rev"; + test_stringify_visitor::test_visitation(v, std::string("Rev")); + + v = (int16_t) 2001; + test_stringify_visitor::test_visitation(v, (int16_t) 2001); + EXPECT_NE(test_stringify_visitor::stringify((uint16_t) 2001), v.visit(test_stringify_visitor())); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, ad_hoc_recursion) +{ + struct left_t; + struct right_t; + + using twisty = variant, boost::recursive_wrapper>; + + struct left_t + { + twisty l; + }; + + struct right_t + { + twisty r; + }; + + auto right = [](twisty&& t = {}) -> twisty + { + right_t r; + r.r = t; + return r; + }; + + auto left = [](twisty&& t = {}) -> twisty + { + left_t l; + l.l = t; + return l; + }; + + struct twisty_counter: variant_static_visitor> + { + std::pair operator()(boost::blank) const + { + return {0, 0}; + } + + std::pair operator()(const left_t& l) const + { + auto count = l.l.visit(twisty_counter()); + count.first += 1; + return count; + } + + std::pair operator()(const right_t& r) const + { + auto count = r.r.visit(twisty_counter()); + count.second += 1; + return count; + } + }; + + const twisty tw = left(left(right(right(left(right(left(right(left())))))))); + + int left_count, right_count; + std::tie(left_count, right_count) = tw.visit(twisty_counter()); + + EXPECT_EQ(5, left_count); + EXPECT_EQ(4, right_count); +} +//------------------------------------------------------------------------------------------------------------------- From 2a7435e026b114d92809959ca51f96c05572cc07 Mon Sep 17 00:00:00 2001 From: koe Date: Sun, 12 Feb 2023 13:00:53 -0600 Subject: [PATCH 2/2] variant: add mutable unwrap and visitation --- src/common/variant.h | 27 +++++++++++++++++++-------- tests/unit_tests/variant.cpp | 23 ++++++++++++++++++++++- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/common/variant.h b/src/common/variant.h index a2121c75e..ffb34e40a 100644 --- a/src/common/variant.h +++ b/src/common/variant.h @@ -62,7 +62,6 @@ namespace tools //// // variant: convenience wrapper around boost::variant with a cleaner interface -// - the value may be assigned to but is otherwise read-only // - the variant is 'optional' - an empty variant will evaluate to 'false' and an initialized variant will be 'true' /// template @@ -70,6 +69,7 @@ struct variant_static_visitor : public boost::static_visitor { /// provide visitation for empty variants /// - add this to your visitor with: using variant_static_visitor::operator(); + [[noreturn]] ResultT operator()(const boost::blank) { variant_static_visitor_blank_err(); } [[noreturn]] ResultT operator()(const boost::blank) const { variant_static_visitor_blank_err(); } }; @@ -107,14 +107,20 @@ public: template bool is_type() const noexcept { return this->index() == this->type_index_of(); } - /// try to get a read-only handle to the embedded value (return nullptr on failure) + /// try to get a handle to the embedded value (return nullptr on failure) template - const T* try_unwrap() const - { - return boost::strict_get(&m_value); - } + T* try_unwrap() noexcept { return boost::strict_get< T>(&m_value); } + template + const T* try_unwrap() const noexcept { return boost::strict_get(&m_value); } - /// get a read-only handle to the embedded value + /// get a handle to the embedded value + template + T& unwrap() + { + T *value_ptr{this->try_unwrap()}; + if (!value_ptr) variant_unwrap_err(); + return *value_ptr; + } template const T& unwrap() const { @@ -131,7 +137,7 @@ public: static constexpr int type_index_of() noexcept { using types = boost::mpl::vector; - using elem = typename boost::mpl::find::type; + using elem = typename boost::mpl::find::type; using begin = typename boost::mpl::begin::type; return boost::mpl::distance::value; } @@ -142,6 +148,11 @@ public: /// apply a visitor to the variant template + typename VisitorT::result_type visit(VisitorT &&visitor) + { + return boost::apply_visitor(std::forward(visitor), m_value); + } + template typename VisitorT::result_type visit(VisitorT &&visitor) const { return boost::apply_visitor(std::forward(visitor), m_value); diff --git a/tests/unit_tests/variant.cpp b/tests/unit_tests/variant.cpp index c22200a8c..d7ded8e4b 100644 --- a/tests/unit_tests/variant.cpp +++ b/tests/unit_tests/variant.cpp @@ -232,8 +232,10 @@ struct test_stringify_visitor: public variant_static_visitor return test_stringify_visitor::stringify(t); } }; -} // anonymous namespace //------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +} // anonymous namespace + //------------------------------------------------------------------------------------------------------------------- TEST(variant, operatorbool) { @@ -295,6 +297,25 @@ TEST(variant, unwrap) EXPECT_THROW(v.unwrap(), std::runtime_error); } //------------------------------------------------------------------------------------------------------------------- +TEST(variant, mutation) +{ + variant v; + v = (uint8_t) 5; + EXPECT_EQ(5, v.unwrap()); + uint8_t &intref{v.unwrap()}; + intref = 10; + EXPECT_EQ(10, v.unwrap()); + EXPECT_TRUE(v.try_unwrap()); + uint8_t *intptr{v.try_unwrap()}; + *intptr = 15; + EXPECT_EQ(15, v.unwrap()); + + const variant &v_ref{v}; + EXPECT_EQ(15, v_ref.unwrap()); + EXPECT_TRUE(v_ref.try_unwrap()); + EXPECT_EQ(15, *(v_ref.try_unwrap())); +} +//------------------------------------------------------------------------------------------------------------------- TEST(variant, index) { variant v;