How Do Boost::Variant and Boost::Any Work

How do boost::variant and boost::any work?

If you read the boost::any documentation they provide the source for the idea: http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf

It's basic information hiding, an essential C++ skill to have. Learn it!

Since the highest voted answer here is totally incorrect, and I have my doubts that people will actually go look at the source to verify that fact, here's a basic implementation of an any like interface that will wrap any type with an f() function and allow it to be called:

struct f_any
{
f_any() : ptr() {}
~f_any() { delete ptr; }
bool valid() const { return ptr != 0; }
void f() { assert(ptr); ptr->f(); }

struct placeholder
{
virtual ~placeholder() {}
virtual void f() const = 0;
};

template < typename T >
struct impl : placeholder
{
impl(T const& t) : val(t) {}
void f() const { val.f(); }
T val;
};
// ptr can now point to the entire family of
// struct types generated from impl<T>
placeholder * ptr;

template < typename T >
f_any(T const& t) : ptr(new impl<T>(t)) {}

// assignment, etc...
};

boost::any does the same basic thing except that f() actually returns typeinfo const& and provides other information access to the any_cast function to work.

Boost.Any vs. Boost.Variant

Have you looked at the comparison in the variant library already?

(Not sure what states from external sources are, so it's kind of hard to say what's more appropriate for you.)

Is it possible to use boost::any or boost::variant with a boost::pool?

Yes, boost pool works with both. You're simply using it wrong.

CAVEAT: There's really no use at all to use a pool allocator with boost::any because it will dynamically allocate the held value outside of the pool.

But this doesn't mean that you can't if you use it right.

malloc only allocates uninitialized memory. It's your error to expect to be able to assign to it as if it were a fully functional instance of the object type that the point implies.

T *i = pool.malloc();
new (i) T();

This fixes it:

Live On Coliru

#include <boost/pool/object_pool.hpp>
#include <boost/any.hpp>
#include <boost/variant.hpp>

template <typename T>
void run_test() {
boost::object_pool<T> pool;

T *i = pool.malloc();
new (i) T();
*i = 1;

T *j = pool.construct(2);

pool.destroy(i);
pool.destroy(j);
}

int main() {
run_test<boost::variant<int, double> >();
run_test<boost::any>();
}

This also runs clean under asan/ubsan and valgrind.

Bonus Question

Is this expected behavior? Does the boost::pool only work for simple C++ types like int, doble, float, etc?

For POD types or trivial types you could get away with eliding the constructor, much like the C++ compiler is allowed to elide them in those cases.

Boost Any to Boost Variant using Boost Preprocessor

Here you go:

#define ANY_TO_VARIANT_OP_VARIANT(typeSeq) \
boost::optional<boost::variant<BOOST_PP_SEQ_ENUM(typeSeq)>>

#define ANY_TO_VARIANT_CONVERT_AND_RETURN(r, data, elem) \
if (any.type() == typeid(elem)) { \
return Ret{boost::any_cast<elem>(any)}; \
}

#define SPECIALIZE_BOOST_ANY_TO_VARIANT(typeSeq) \
template<> \
ANY_TO_VARIANT_OPT_VARIANT(typeSeq) anyToVariant(const boost::any& any) { \
using Ret = ANY_TO_VARIANT_OPT_VARIANT(typeSeq); \
BOOST_PP_SEQ_FOR_EACH(ANY_TO_VARIANT_CONVERT_AND_RETURN, ~, typeSeq) \
return Ret{}; \
}

Usage:

SPECIALIZE_BOOST_ANY_TO_VARIANT((int)(double)(std::string))

See it live on Coliru

How to properly replace boost::variant by std::variant?

As you have duplicate types in your variant some of the constructors are disabled:

This overload only participates in overload resolution if there is exactly one occurrence of T in Types...

You need to use the constructor with an explicit type index:

return std::variant<T...>(std::in_place_index<n>, std::get<n>(tpl));

Your original boost code has undefined behaviour:

Each type specified as a template argument to variant must be distinct after removal of qualifiers. Thus, for instance, both variant<int, int> and variant<int, const int> have undefined behavior.

The standard libary implementation does support duplicate types and therefore prevents you from accidentally constructing an ambigous variant. For example what should the following do:

variant<std::string, double, double, int> t = 4.5;

With boost it is UB, either of the double values might be initialised or it might do something completely different. The standard library explitly makes this a compiler error so that you have to choose which of your doubles you want to initialise.

What are the differences between std::variant and boost::variant?

  • Assignment/emplacement behavior:

    • boost::variant may allocate memory when performing assignment into a live variant. There are a number of rules that govern when this can happen, so whether a boost::variant will allocate memory depends on the Ts it is instantiated with.

    • std::variant will never dynamically allocate memory. However, as a concession to the complex rules of C++ objects, if an assignment/emplacement throws, then the variant may enter the "valueless_by_exception" state. In this state, the variant cannot be visited, nor will any of the other functions for accessing a specific member work.

      You can only enter this state if assignment/emplacement throws.

  • Boost.Variant includes recursive_variant, which allows a variant to contain itself. They're essentially special wrappers around a pointer to a boost::variant, but they are tied into the visitation machinery.

    std::variant has no such helper type.

  • std::variant offers more use of post-C++11 features. For example:

    • It forwards the noexcept status of the special member functions of its constituent types.

    • It has variadic template-based in-place constructors and emplacement functions.

    • Defect resolutions applied to C++17 may mean that it will also forward trivial copyability of its types. That is, if all of the types are trivially copyable, then so too will variant<Ts>.

Is it possible to create Boost multi_index MEM_FUN key extractors for a container of Boost variant?

The best way to approach this is to provide your own user-defined key extractor. Note that the fact that the variant types are derived from a common base does not play any significant role here: in a scenario without inheritance, simply apply a regular visitor that takes care of all the types in the variant (possibly with a generic lambda if all the types conform to the same syntax for getting the key).

Live Coliru Demo

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/variant/variant.hpp>
#include <utility>

struct base
{
virtual ~base()=default;
virtual std::pair<char,char> extractKey()const=0;
};

struct derived1:base
{
std::pair<char,char> extractKey()const override{return{1,0};};
};

struct derived2:base
{
std::pair<char,char> extractKey()const override{return{0,1};};
};

using namespace boost::multi_index;

using variant=boost::variant<derived1,derived2>;

struct variant_key
{
using result_type=std::pair<char,char>;

auto operator()(const variant& x)const
{
return boost::apply_visitor(
[](const base& b){return b.extractKey();},
x
);
}
};

using container=multi_index_container<
variant,
indexed_by<
ordered_non_unique<variant_key>
>
>;

// testing

#include <iostream>

template<typename... Ts> struct overloaded:Ts...{using Ts::operator()...;};
template<typename... Ts> overloaded(Ts...)->overloaded<Ts...>;

int main()
{
container c;
for(int i=2;i--;){
c.insert(variant(derived1()));
c.insert(variant(derived2()));
}

for(const auto& x:c){
boost::apply_visitor(
overloaded{
[](const derived1&){std::cout<<"derived1 ";},
[](const derived2&){std::cout<<"derived2 ";}
},
x
);
}
}

Output

derived2 derived2 derived1 derived1 


Related Topics



Leave a reply



Submit