Boost Variant: как получить текущий удерживаемый тип?

Насколько я понял, анализируются все типы boost.variant в реальные типы (имеется в виду, как если бы boost variant<int, string> a; a="bla-bla" после компиляции превратился бы в string a; a="bla-bla") И вот мне интересно: как получить, какой тип был помещен в вариант boost?

Что я пробовал:

#include <boost/variant.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

int main()
{
    typedef boost::function<double (double x)> func0;
    typedef boost::function<double (double x, double y)> func1;
    typedef boost::variant<int, func0, func1> variant_func;
    func1 fn = std::plus<double>();
    variant_func v(fn);
    std::cout << boost::get<func1>(v)(1.0, 1.0) << std::endl; // this works
    //std::cout << boost::get<v::type>(v)(1.0, 1.0) << std::endl; // this does not compile with many errors
    // std::cout << (v)(1.0, 1.0) << std::endl; // this fails with Error    1   error C2064: term does not evaluate to a function taking 2 arguments

    std::cin.get();
    return 0;
}

person myWallJSON    schedule 01.12.2011    source источник


Ответы (4)


v.which() вернет отсчитываемый от 0 индекс типа удерживаемого в данный момент объекта.

Когда вы извлекаете объект, ваш код должен использовать статический тип (чтобы удовлетворить шаблон функции get<T>) для ссылки на (эффективно) динамически типизированный объект.

Вам нужно либо проверить тип (используя which() или type()) и соответствующим образом перейти, либо использовать статического посетителя. Независимо от того, какой способ вы выберете, вы должны явно указать статический тип, который вы хотите получить, и он должен соответствовать динамическому типу, иначе будет выдано исключение.

Один из способов обойти эту проблему — вместо прямого использования вариантного типа использовать класс, который внутри содержит вариантный тип, а затем определяет все операторы неявного преобразования, необходимые для использования объекта с минимальными усилиями.

У меня есть проект под названием Dynamic C++, в котором используется этот метод.

person Ferruccio    schedule 01.12.2011
comment
Может ли это помочь заставить boost::get‹T›(v)(1.0, 1.0) или (v)(1.0, 1.0) работать? - person myWallJSON; 01.12.2011
comment
Нет. Вам нужно использовать синтаксис get‹T›() или статический посетитель, чтобы получить содержимое варианта. - person Ferruccio; 01.12.2011
comment
Поддерживает ли Dynamic C++ какие-либо типы (например, тип int(*)(std::string, int))? - person myWallJSON; 01.12.2011
comment
Нет, у него фиксированный набор типов. Но добавлять новые типы довольно просто, хотя и утомительно. Особенно, если вы хотите разрешить операции между типами. - person Ferruccio; 01.12.2011
comment
+1 для динамического С++, это не мое, но я посмотрел, и он выглядит очень чистым. Отличная работа! - person sehe; 02.12.2011

boost.variant имеет функцию .type() который может возвращать typeid активного типа, если вы включили RTTI.

Вы также можете определить статического посетителя для выполнения действий в зависимости от типа содержимого варианта, например.

struct SomeVisitor : public boost::static_visitor<double>
{
    double operator()(const func0& f0) const { return f0(1.0); }
    double operator()(const func1& f1) const { return f1(1.0, 1.0); }
    double operator()(int integer) const { return integer; }
};
...
std::cout << boost::apply_visitor(SomeVisitor(), v) << std::endl;
person kennytm    schedule 01.12.2011
comment
поэтому можно будет вызвать `boost::get‹v.type()›(v)(1.0, 1.0)`, если RTTI включен? - person myWallJSON; 01.12.2011
comment
@myWallJSON: Нет, потому что аргумент шаблона должен быть типом, известным во время компиляции, а не объектом typeid во время выполнения. - person Mike Seymour; 01.12.2011
comment
так что могут быть какие-то кастинги типа ((v.type()) v)(1.0, 1.0) ? - person myWallJSON; 01.12.2011
comment
@myWallJSON: к хранимому значению лучше всего обращаться с помощью static_visitor, как показано выше. Если вы используете get, то это означает, что вы статически знаете, какой тип в данный момент хранится, или вы пытаетесь получить значение как тип, а затем имеете дело с указателем NULL или исключением. - Результаты RTTI нельзя использовать для каких-либо вещей во время компиляции (например, приведения). Идентификатор типа — это объект, содержащий описание типа во время выполнения, он не дает имя типа, которое можно использовать во время компиляции. - person UncleBens; 01.12.2011
comment
@myWallJSON: вы можете сделать boost::get<X>(variant)(1.0, 1.0);, но вам лучше заключить его в блок try, потому что, если variant не хранит объект типа X, будет выдано исключение. - person UncleBens; 01.12.2011
comment
@UncleBens: Или используйте boost::get<X>(&variant), а затем проверьте результат на ноль. - person kennytm; 01.12.2011

Вы можете использовать следующее, что оба приводят к объектам std::type_info:

  • функция-член type() для boost::variant,
  • оператор C++ typeid(), который можно применить к любому типу или типизированному выражению,

вместе с функцией-членом std::type_info::operator==, чтобы проверить, какой тип в данный момент хранится в boost::variant. Например,

boost::variant<int, bool, std::string> container;
container = "Hello world";

if (container.type() == typeid(std::string)) {
    std::cout << "Found a string: " << boost::get<std::string>(container);
}
else if (container.type() == typeid(int)) {
    std::cout << "Found an int: " << boost::get<int>(container);
}
person richardr    schedule 12.11.2013
comment
Неа! Это приведет к сбою и сгоранию, если вектор был включен в вариант. т.е. вариант‹vector‹int›, int › ›. Вместо этого используйте which(), как указывает Ферруччо. - person TimZaman; 01.06.2014
comment
Я пробовал с boost::variant<vector<int>, int> в MSVS 2013, и это сработало. С каким компилятором вы пробовали? Можете ли вы объяснить, почему вы думаете, что это не должно работать в некоторых случаях? - person richardr; 01.06.2014

Вы можете использовать версию указателя boost::get. руководство имеет этот пример:

void times_two( boost::variant< int, std::string > & operand )
{
    if ( int* pi = boost::get<int>( &operand ) )
        *pi *= 2;
    else if ( std::string* pstr = boost::get<std::string>( &operand ) )
        *pstr += *pstr;
}

Таким образом, вы используете его так же, как обычно используете boost::get, но вместо этого передаете указатель на вариант, и результатом является указатель, который имеет значение nullptr, если это не тот тип, который в настоящее время хранится в варианте. Бесполезно, если этот тип появляется более одного раза в списке типов в варианте, но это не очень распространено.

person Arthur Tacca    schedule 09.07.2020