variant: visit lambda and value-initialize by index

This commit is contained in:
jeffro256 2024-02-13 18:36:06 -06:00
parent 059028a30a
commit 62fdacca07
No known key found for this signature in database
GPG Key ID: 6F79797A6E392442
2 changed files with 132 additions and 2 deletions

View File

@ -37,6 +37,7 @@
#include <boost/mpl/begin_end.hpp>
#include <boost/mpl/distance.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/none_t.hpp>
#include <boost/variant/apply_visitor.hpp>
@ -54,6 +55,18 @@
namespace tools
{
namespace detail
{
template <class Variant>
struct value_initialize_on_which
{
template <typename T>
void operator()(T) { if (Variant::template type_index_of<T>() == 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>
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<VisitorT>(visitor), m_value);
}
template <typename VisitorT>
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<VisitorT>(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<typename VType::types>::type::value)
throw std::runtime_error("value_initialize_to_type_index: type index of out range");
detail::value_initialize_on_which<variant> viow{*this, which};
boost::mpl::for_each<typename VType::types>(viow);
}
private:
//member variables
/// variant of all value types

View File

@ -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<decltype(x), std::string>)
return x;
else if constexpr (std::is_same_v<decltype(x), boost::blank>)
throw std::runtime_error("boost blank cannot be stringified");
else
return std::to_string(x);
};
variant<int8_t, uint8_t, int16_t, uint16_t, std::string> 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<const int&>
{
using tools::variant_static_visitor<const int&>::operator();
const int& operator()(const A &a) const { return a.x; }
const int& operator()(const B &b) const { return b.x; }
};
tools::variant<A, B> 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<const char*>(std::addressof(v.visit(x_ref_visitor{})));
const char * const pv = reinterpret_cast<const char*>(&v);
EXPECT_LT(px - pv, sizeof(v));
}
//-------------------------------------------------------------------------------------------------------------------
TEST(variant, value_initialize_to_type_index)
{
variant<int8_t, uint8_t, int16_t, uint16_t, std::string> 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<int8_t>());
v.value_initialize_to_type_index(1);
EXPECT_EQ(1, v.index());
EXPECT_EQ(0, v.unwrap<int8_t>());
v = (uint8_t) 69;
EXPECT_EQ(2, v.index());
EXPECT_EQ(69, v.unwrap<uint8_t>());
v.value_initialize_to_type_index(2);
EXPECT_EQ(2, v.index());
EXPECT_EQ(0, v.unwrap<uint8_t>());
v = (int16_t) 69;
EXPECT_EQ(3, v.index());
EXPECT_EQ(69, v.unwrap<int16_t>());
v.value_initialize_to_type_index(3);
EXPECT_EQ(3, v.index());
EXPECT_EQ(0, v.unwrap<int16_t>());
v = (uint16_t) 69;
EXPECT_EQ(4, v.index());
EXPECT_EQ(69, v.unwrap<uint16_t>());
v.value_initialize_to_type_index(4);
EXPECT_EQ(4, v.index());
EXPECT_EQ(0, v.unwrap<uint16_t>());
v = std::string("69");
EXPECT_EQ(5, v.index());
EXPECT_EQ("69", v.unwrap<std::string>());
v.value_initialize_to_type_index(5);
EXPECT_EQ(5, v.index());
EXPECT_EQ("", v.unwrap<std::string>());
v = (int16_t) 69;
v.value_initialize_to_type_index(5);
EXPECT_EQ("", v.unwrap<std::string>());
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;