diff --git a/src/common/variant.h b/src/common/variant.h index a7e2637e5..5b9a1753d 100644 --- a/src/common/variant.h +++ b/src/common/variant.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,18 @@ namespace tools { +namespace detail +{ +template +struct value_initialize_on_which +{ + template + void operator()(T) { if (Variant::template type_index_of() == target_which) v = T(); } + + Variant &v; + const int target_which; +}; +} // namespace detail [[noreturn]] inline void variant_static_visitor_blank_err() { throw std::runtime_error("variant: tried to visit an empty variant."); } @@ -148,16 +161,26 @@ public: /// apply a visitor to the variant template - typename VisitorT::result_type visit(VisitorT &&visitor) + decltype(auto) visit(VisitorT &&visitor) // decltype(auto) since it forwards the return ref type correctly { return boost::apply_visitor(std::forward(visitor), m_value); } template - typename VisitorT::result_type visit(VisitorT &&visitor) const + decltype(auto) visit(VisitorT &&visitor) const // decltype(auto) since it forwards the return ref type correctly { return boost::apply_visitor(std::forward(visitor), m_value); } + /// value initialize the variant based on a type index + void value_initialize_to_type_index(const int which) + { + if (which < 0 || which >= boost::mpl::size::type::value) + throw std::runtime_error("value_initialize_to_type_index: type index of out range"); + + detail::value_initialize_on_which viow{*this, which}; + boost::mpl::for_each(viow); + } + private: //member variables /// variant of all value types diff --git a/tests/unit_tests/variant.cpp b/tests/unit_tests/variant.cpp index 21e1f2309..3b0c96c4f 100644 --- a/tests/unit_tests/variant.cpp +++ b/tests/unit_tests/variant.cpp @@ -372,6 +372,113 @@ TEST(variant, visit) EXPECT_NE(test_stringify_visitor::stringify((uint16_t) 2001), v.visit(test_stringify_visitor())); } //------------------------------------------------------------------------------------------------------------------- +TEST(variant, visit_lambda) +{ + const auto stringify_lambda = [](auto x) -> std::string + { + if constexpr (std::is_same_v) + return x; + else if constexpr (std::is_same_v) + throw std::runtime_error("boost blank cannot be stringified"); + else + return std::to_string(x); + }; + + variant v; + EXPECT_THROW(v.visit(stringify_lambda), std::runtime_error); + + v = "Rev"; + EXPECT_EQ("Rev", v.visit(stringify_lambda)); + + v = (int16_t) 2001; + EXPECT_EQ("2001", v.visit(stringify_lambda)); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, visit_ref_passthru) +{ + struct A + { + int x; + }; + + struct B + { + int x; + }; + + struct x_ref_visitor: tools::variant_static_visitor + { + using tools::variant_static_visitor::operator(); + + const int& operator()(const A &a) const { return a.x; } + const int& operator()(const B &b) const { return b.x; } + }; + + tools::variant v; + EXPECT_THROW(v.visit(x_ref_visitor{}), std::runtime_error); + + // A very hairy looking test, but we're just testing that the reference returned from our static + // visitor is actually pointing to something in the same stack space as our variant operand. + // This will let us catch mistakes where we take a reference to a locally created variable if + // the visit() method is changed subtlely. + v = A { 2024 }; + const char * const px = reinterpret_cast(std::addressof(v.visit(x_ref_visitor{}))); + const char * const pv = reinterpret_cast(&v); + EXPECT_LT(px - pv, sizeof(v)); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(variant, value_initialize_to_type_index) +{ + variant v; + for (int i = 0; i < 6; ++i) + { + v.value_initialize_to_type_index(i); + EXPECT_EQ(i, v.index()); + } + + v = (int8_t) 69; + EXPECT_EQ(1, v.index()); + EXPECT_EQ(69, v.unwrap()); + v.value_initialize_to_type_index(1); + EXPECT_EQ(1, v.index()); + EXPECT_EQ(0, v.unwrap()); + + v = (uint8_t) 69; + EXPECT_EQ(2, v.index()); + EXPECT_EQ(69, v.unwrap()); + v.value_initialize_to_type_index(2); + EXPECT_EQ(2, v.index()); + EXPECT_EQ(0, v.unwrap()); + + v = (int16_t) 69; + EXPECT_EQ(3, v.index()); + EXPECT_EQ(69, v.unwrap()); + v.value_initialize_to_type_index(3); + EXPECT_EQ(3, v.index()); + EXPECT_EQ(0, v.unwrap()); + + v = (uint16_t) 69; + EXPECT_EQ(4, v.index()); + EXPECT_EQ(69, v.unwrap()); + v.value_initialize_to_type_index(4); + EXPECT_EQ(4, v.index()); + EXPECT_EQ(0, v.unwrap()); + + v = std::string("69"); + EXPECT_EQ(5, v.index()); + EXPECT_EQ("69", v.unwrap()); + v.value_initialize_to_type_index(5); + EXPECT_EQ(5, v.index()); + EXPECT_EQ("", v.unwrap()); + + v = (int16_t) 69; + v.value_initialize_to_type_index(5); + EXPECT_EQ("", v.unwrap()); + + EXPECT_THROW(v.value_initialize_to_type_index(-1), std::runtime_error); + EXPECT_THROW(v.value_initialize_to_type_index(6), std::runtime_error); +} +//------------------------------------------------------------------------------------------------------------------- TEST(variant, ad_hoc_recursion) { struct left_t;