Boost Variant: 如何获取当前持有的类型?

43

据我理解,所有类型的boost.variant都会被解析为实际类型(即如果编译后 boost variant<int, string> a; a="bla-bla" 将变成 string a; a="bla-bla")。因此我想知道如何获取放入 boost variant 的类型。

我尝试过以下方法:

#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;
}
4个回答

37

v.which()将返回当前持有对象类型的基于0的索引。

当检索对象时,您的代码必须使用静态类型(以满足get<T>函数模板)来引用实际上是动态类型的对象。

您需要测试类型(使用which()type()),并根据情况进行分支处理,或使用静态访问者。无论选择哪种方式,都必须明确声明要检索的静态类型,并且它必须与动态类型匹配,否则将抛出异常。

解决此问题的一种方法是,不直接使用变体类型,而是使用一个包含内部变体类型并定义任何必要的隐式转换运算符以最小化使用对象的类。

我有一个名为Dynamic C++的项目,使用了这种技术。


它能帮助让 boost::get<T>(v)(1.0, 1.0) 或 (v)(1.0, 1.0) 正常工作吗? - myWallJSON
不行。您需要使用get<T>()语法或静态访问器来检索变量的内容。 - Ferruccio
Dynamic C++是否支持任何类型(例如int(*)(std::string, int)类型)? - myWallJSON
不,它有一个固定的类型集。但是,如果你想要允许不同类型之间的操作,那么很容易(虽然繁琐)添加新类型。 - Ferruccio
2
对于动态C++,我个人不是很喜欢,但我看了一下,它看起来非常干净。做得好! - sehe

18

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;

如果启用了RTTI,那么应该可以调用boost::get<v.type()>(v)(1.0, 1.0)吗? - myWallJSON
@myWallJSON:不行,因为模板参数必须是在编译时已知的类型,而不是运行时的typeid对象。 - Mike Seymour
那么可能是任何类型的转换,比如 ((v.type()) v)(1.0, 1.0) - myWallJSON
1
@myWallJSON:存储的值最好使用上面显示的static_visitor进行访问。如果您使用get,则意味着您静态地知道当前存储的类型,或者尝试将该值作为类型获取,然后处理NULL指针或异常。- RTTI结果不能用于任何编译时的事情(如强制转换)。typeid是一个包含类型运行时描述的对象,它不会产生可以在编译时使用的类型名。 - UncleBens
@myWallJSON:你可以使用boost::get<X>(variant)(1.0, 1.0);,但最好将其包装在一个try块中,因为如果variant未存储类型为X的对象,则会抛出异常。 - UncleBens
@UncleBens:或者使用 boost::get<X>(&variant),然后对结果进行空指针检查。 - kennytm

15

以下两种方法都能得到 std::type_info 对象:

  • 使用 boost::variant 的 type() 成员函数。
  • 对于任何类型或类型表达式,可以使用 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);
}

不行!如果变量中包含向量,例如 variant<vector<int>, int>>,这将导致崩溃和失败。像 Ferruccio 所指出的那样,应该使用 which()。 - TimZaman
8
我尝试在 MSVS 2013 上使用 boost::variant<vector<int>, int> 并且成功了。你用的是哪个编译器?你能解释一下为什么你认为对于某些情况它不能成功吗? - richardr

1
你可以使用指针版本的boost::gettutorial有这个例子:
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的方式相同,但是传递一个指向variant的指针,结果是一个指针,如果该类型当前未存储在variant中,则为nullptr。如果该类型在variant的类型列表中出现多次,则它将不再有用,但这并不常见。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接