3

Assume that you have a boost::any object and a boost::variant object.

I'm looking for a generic function convert, that takes a template parameter T being a specialized boost::variant e.g. boost::variant<int, std::string> and magically converts the boost::any to one of the available types of the given boost::variant.

template<T>
T convert(const boost::any& any) {
   // Some generic conversion code here or throw exception if conversion is not possible!
}

int main(int argc, char** args) {
    typedef boost::variant<int, std::string> TVar;

    boost::any any="Hello World";
    TVar variant=convert<TVar>(any);
    // variant contains "Hello World"
    return 0;
}

I'm wondering if it is possible to write such a function or if it might be impossible for some reason?

Aleph0
  • 5,816
  • 4
  • 29
  • 80
  • Something like `return any_cast(&any)`? You have to catch exceptions and in such a case you can return a default constructed `T` or whatever, but is that what you want? – skypjack Feb 12 '16 at 08:16
  • The point is, that the method convert shouldn't know beforehand what kind of boost::variant<....> it will receive. Hence you can not implement convert using a chain of ifs containing `any_cast(&any)`. – Aleph0 Feb 12 '16 at 10:10

2 Answers2

2

Let's enclose all code in struct templated by variant type

template<class VAR>
struct ExtractorGenerator
{
    using Extractor = std::function<boost::optional<VAR>(boost::any const &)>;
    std::vector<Extractor> extractors;

    template<class T> 
    static boost::optional<VAR> tryCast(boost::any const & arg);

    template<class T> 
    void operator()(T);
};

You can easily write a function that for given type tries to convert boost::any to variant of this type

template<class VAR>
template<class T> 
boost::optional<VAR> ExtractorGenerator<VAR>::tryCast(boost::any const & arg)
{ 
    T const * val = boost::any_cast<T>(&arg);
    return val == nullptr ? boost::none : boost::make_optional(VAR{*val});
}

Now using boost::mpl you can iterate through all variant types to generate function for each variant's type

template<class VAR>
template<class T> void ExtractorGenerator<VAR>::operator()(T)
{
    extractors.push_back(Extractor::tryCast<T>);
}

typedef boost::variant<int, std::string, char> MyVariant;
ExtractorGenerator<MyVariant> generator;
boost::mpl::for_each<MyVariant::types>(boost::ref(generator));

And now you just apply all created functions:

std::vector<MyVariant> extractedVals;
for (auto fun : extractor.extractors)
{
    boost::optional<MyVariant> extracted = fun(val);
    if (extracted)
        extractedVals.push_back(extracted.get());
}
Tadeusz Kopec for Ukraine
  • 12,283
  • 6
  • 56
  • 83
  • Thanks for your help. The line `using Extractor = std::function(boost::any const &)>;` is not compiling with my MSVC 2010 Compiler. Any idea, what you wanted to express? – Aleph0 Feb 15 '16 at 06:35
  • Your solution finally worked for me, after slightly changing your posted code. Many thanks again. – Aleph0 Feb 15 '16 at 07:06
  • @FrankSimon I reorganized code as it was a bit messy. The line you are asking about is equvalent to `typedef std::function(boost::any const &)> Extractor` but uses a type alias syntax which is preferred in C++11. – Tadeusz Kopec for Ukraine Feb 15 '16 at 09:11
  • Thanks. Just wanted to say, that your solution is very nice and elegant. I'm wondering if this function is a missing part in the boost libraries. – Aleph0 Feb 15 '16 at 09:58
2

You can call boost::any_cast for each of the types within boost::variant and stop when the first cast succeeded:

#include <iostream>
#include <utility>
#include <stdexcept>
#include <sstream>

#include <boost/any.hpp>
#include <boost/variant.hpp>
#include <boost/type_index.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/at.hpp>

template <typename Sequence>
struct mpl_sequence_to_std_tuple
{
template <std::size_t... Is>
static auto x(std::index_sequence<Is...>) -> std::tuple<typename boost::mpl::at_c<Sequence, Is>::type...>;

using type = decltype(x(std::make_index_sequence<boost::mpl::size<Sequence>::type::value>{}));
};

struct signal_conversion_success{};

template <typename T, typename Variant>
void try_convert(const boost::any& any, Variant& var)
{
    try
    {
        var = boost::any_cast<T>(any);
        throw signal_conversion_success{};
    }
    catch(const boost::bad_any_cast &)
    {
    }
}

template <typename T, typename... Ts>
std::string parameter_pack_to_string(const std::string& separator = ", ")
{
    std::stringstream ss;
    ss << boost::typeindex::type_id<T>().pretty_name();
    auto l = {0, (void(ss << separator << boost::typeindex::type_id<Ts>().pretty_name()),0)...};
    std::ignore = l;
    return ss.str();
}

template <typename Variant, typename...Ts>
void do_convert(const boost::any& any, Variant& var, std::tuple<Ts...>)
{
    bool success = false;

    try {
        auto l = {0, (void(try_convert<Ts>(any, var)), 0)... };
        std::ignore = l;
    }
    catch(const signal_conversion_success&)
    {
        success = true;
    }

    if (!success)
    {
        std::stringstream ss;
        ss << "cannot convert this boost::any instance to any of the following types: ";
        ss << parameter_pack_to_string<Ts...>();
        throw std::invalid_argument(ss.str());
    }
}

template<typename Variant>
void convert(const boost::any& any, Variant& var)
{
  using Tuple = typename mpl_sequence_to_std_tuple<typename Variant::types>::type;
  do_convert(any, var, Tuple{});
}

struct print_visitor : public boost::static_visitor<void>
{
    template <typename T>
    void operator()(T&& t) const
    {
        std::cout << boost::typeindex::type_id<T>().pretty_name() << ": " << std::forward<T>(t) << std::endl;
    }
};

int main()
{
    using Variant = boost::variant<int, std::string>;
    boost::any any = std::string("Hello World");
    Variant var;
    convert(any, var);
    boost::apply_visitor(print_visitor(), var);
}

live example

In case none of the casts succeeded, an exception is thrown, see the following live example.

m.s.
  • 16,063
  • 7
  • 53
  • 88
  • 1
    Many thanks for this genius solution. Unfortunately, I'm stuck with MSVC 2010, but we are right now migrating to MSVC 2015. Up to now MSVC 2010 has no support for variadic templates. Sorry, that I didn't mentioned that. Still the solution seems to work in the live example. – Aleph0 Feb 15 '16 at 06:38