Общая функция для преобразования boost::any в boost::variant

Предположим, что у вас есть объект boost::any и объект boost::variant.

Я ищу общую функцию convert, которая принимает параметр шаблона T, являющийся специализированным boost::variant, например. boost::variant<int, std::string> и волшебным образом преобразует boost::any в один из доступных типов данного 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;
}

Мне интересно, можно ли написать такую ​​функцию или по какой-то причине это невозможно?


person Aleph0    schedule 12.02.2016    source источник
comment
Что-то вроде return any_cast<T>(&any)? Вы должны перехватывать исключения, и в таком случае вы можете вернуть сконструированный по умолчанию T или что-то еще, но этого ли вы хотите?   -  person skypjack    schedule 12.02.2016
comment
Дело в том, что метод convert не должен заранее знать, какой boost::variant‹....› он получит. Следовательно, вы не можете реализовать convert, используя цепочку if, содержащую any_cast<T>(&any).   -  person Aleph0    schedule 12.02.2016


Ответы (2)


Давайте заключим весь код в структуру, шаблонную по типу варианта

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);
};

Вы можете легко написать функцию, которая для данного типа пытается преобразовать boost::any в вариант этого типа.

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});
}

Теперь, используя boost::mpl, вы можете перебирать все типы вариантов, чтобы сгенерировать функцию для каждого типа варианта.

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));

И теперь вы просто применяете все созданные функции:

std::vector<MyVariant> extractedVals;
for (auto fun : extractor.extractors)
{
    boost::optional<MyVariant> extracted = fun(val);
    if (extracted)
        extractedVals.push_back(extracted.get());
}
person Tadeusz Kopec    schedule 12.02.2016
comment
Спасибо за вашу помощь. Строка using Extractor = std::function<boost::optional<VAR>(boost::any const &)>; не компилируется моим компилятором MSVC 2010. Есть идеи, что вы хотели выразить? - person Aleph0; 15.02.2016
comment
Ваше решение, наконец, сработало для меня после небольшого изменения вашего опубликованного кода. Большое спасибо еще раз. - person Aleph0; 15.02.2016
comment
@FrankSimon Я реорганизовал код, так как он был немного беспорядочным. Строка, о которой вы спрашиваете, эквивалентна typedef std::function<boost::optional<VAR>(boost::any const &)> Extractor, но использует синтаксис псевдонима типа, который предпочтительнее в С++ 11. - person Tadeusz Kopec; 15.02.2016
comment
Спасибо. Просто хотел сказать, что ваше решение очень красивое и элегантное. Мне интересно, является ли эта функция отсутствующей частью в библиотеках повышения. - person Aleph0; 15.02.2016

Вы можете вызвать boost::any_cast для каждого из типов в пределах boost::variant и остановиться, когда первое приведение будет успешным:

#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);
}

живой пример

Если ни одно из приведений не удалось, создается исключение, см. следующий живой пример.

person m.s.    schedule 12.02.2016
comment
Большое спасибо за это гениальное решение. К сожалению, я застрял с MSVC 2010, но сейчас мы переходим на MSVC 2015. До сих пор MSVC 2010 не поддерживает шаблоны с переменным числом аргументов. Извините, что я не упомянул об этом. Тем не менее решение работает в живом примере. - person Aleph0; 15.02.2016