我想使用boost.variant<T0,T1,T2>
作为模板“访问者”类的参数,该类将根据boost.variant访问者机制的要求提供访问者操作符,在这种情况下,所有返回的void,即
void operator()(T0 value);
void operator()(T1 value);
void operator()(T2 value);
该模板还将为变体中的每个类型T0 ...提供相应的虚函数,默认情况下不执行任何操作。用户可以从模板类继承并仅重新定义他感兴趣的虚拟函数。这类似于众所周知的“模板方法”模式。 我能够想出的唯一解决方案是将boost :: variant和关联的访问者包装在一个模板中,并通过typedef访问它们。这没关系,但感觉有点笨重。这是代码:
#include "boost/variant.hpp"
//create specializations of VariantWrapper for different numbers of variants -
//just show a template for a variant with three types here.
//variadic template parameter list would be even better!
template<typename T0, typename T1, typename T2>
struct VariantWrapper
{
//the type for the variant
typedef boost::variant<T0,T1,T2> VariantType;
//The visitor class for this variant
struct Visitor : public boost::static_visitor<>
{
void operator()(T0 value)
{
Process(value);
}
void operator()(T1 value)
{
Process(value);
}
void operator()(T2 value)
{
Process(value);
}
virtual void Process(T0 val){/*do nothing */}
virtual void Process(T1 val){/*do nothing */}
virtual void Process(T2 val){/*do nothing */}
protected:
Visitor(){}
};
typedef Visitor VisitorType;
private:
VariantWrapper(){}
};
然后按如下方式使用该类:
typedef VariantWapper<bool,int,double> VariantWrapperType;
typedef VariantWrapperType::VariantType VariantType;
typedef VariantWrapperType::VisitorType VisitorType;
struct Visitor : public VisitorType
{
void Process(bool val){/*do something*/}
void Process(int val){/*do something*/}
/* this class is not interested in the double value */
};
VariantType data(true);
apply_visitor(Visitor(),data);
正如我所说,这似乎工作正常,但如果我不必创建一个特殊的包装类来将变量和访问者联系在一起,我会更喜欢它。我希望能够直接使用boost.variant来实例化模板访问者类。我已经看过使用类型参数,非类型参数和模板模板参数,但似乎没有任何建议。我想做的不可能吗?我可能会遗漏一些东西,如果有人对此有任何意见,我会很感激。
答案 0 :(得分:15)
使用Boost Variant和虚拟调度的代码有点可疑。特别是考虑到您在编译期间知道您对处理感兴趣的内容,并且完全没有必要在运行时创建虚拟表以实现您的目标。
我建议您使用partial template specialization。因此,有一个默认的模板方法,可以接受变体中的任何类型,并且什么都不做。对于您感兴趣的那些类型,只需专门化模板。
这是一个例子。我们有三种类型 - Foo,Bar和War。我们只对最后两种类型感兴趣,并对它们有专业化。所以Foo被忽略了。
#include <iostream>
#include <boost/variant.hpp>
using namespace std;
using namespace boost;
struct Foo {};
struct Bar {};
struct War {};
typedef variant<Foo, Bar, War> Guess;
struct Guesstimator : public boost::static_visitor<void>
{
template <typename T>
void operator () (T) const
{
}
};
template <>
inline void
Guesstimator::operator () <Bar> (Bar) const
{
cout << "Let's go to a pub!" << endl;
}
template <>
inline void
Guesstimator::operator () <War> (War) const
{
cout << "Make love, not war!" << endl;
}
以下是一个简单的用法示例:
int
main ()
{
Guess monday;
apply_visitor (Guesstimator (), monday);
War war;
Guess ww2 (war);
apply_visitor (Guesstimator (), ww2);
Bar irishPub;
Guess friday (irishPub);
apply_visitor (Guesstimator (), friday);
}
该计划的输出将是:
Make love, not war!
Let's go to a pub!
这是另一种解决方案。我们创建一个默认访问者,忽略所有内容,除了您在类型列表中指定的内容。这不方便,因为您必须指定两次类型列表 - 一次在类型列表中,然后在每个处理方法(运算符)中。此外,通用模板实际上将继承您的访问者。但是,我们走了:
#include <cstddef>
#include <iostream>
#include <boost/variant.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/utility/enable_if.hpp>
// Generic visitor that does magical dispatching of
// types and delegates passes down to your visitor only
// those types specified in a type list.
template <typename Visitor, typename TypeList>
struct picky_visitor :
public boost::static_visitor<void>,
public Visitor
{
template <typename T>
inline void
operator () (T v, typename boost::enable_if< typename boost::mpl::contains< TypeList, T >::type >::type *dummy = NULL) const
{
Visitor::operator () (v);
}
template <typename T>
inline void
operator () (T v, typename boost::disable_if<typename boost::mpl::contains< TypeList, T >::type >::type *dummy = NULL) const
{
}
};
// Usage example:
struct nil {};
typedef boost::variant<nil, char, int, double> sql_field;
struct example_visitor
{
typedef picky_visitor< example_visitor, boost::mpl::vector<char, int> > value_type;
inline void operator () (char v) const
{
std::cout << "character detected" << std::endl;
}
inline void operator () (int v) const
{
std::cout << "integer detected" << std::endl;
}
};
int
main ()
{
example_visitor::value_type visitor;
sql_field nilField;
sql_field charField ('X');
sql_field intField (1986);
sql_field doubleField (19.86);
boost::apply_visitor (visitor, nilField);
boost::apply_visitor (visitor, charField);
boost::apply_visitor (visitor, intField);
boost::apply_visitor (visitor, doubleField);
}
答案 1 :(得分:1)
随着时间的推移,新的和有趣的图书馆发展。这个问题已经过时了,但从那时起,我个人的解决方案远远优于迄今为止提供的解决方案。
优秀的Mach7库,允许前所未有的匹配(以及访问)功能。它由Yuriy Solodkyy,Gabriel Dos Reis和Bjarne Stroustrup本人撰写。对于在这个问题上磕磕绊绊的人,这里有一个来自自述文件的例子:
void print(const boost::variant<double,float,int>& v)
{
var<double> d; var<float> f; var<int> n;
Match(v)
{
Case(C<double>(d)) cout << "double " << d << endl; break;
Case(C<float> (f)) cout << "float " << f << endl; break;
Case(C<int> (n)) cout << "int " << n << endl; break;
}
EndMatch
}
我现在正在使用它,到目前为止,使用起来非常愉快。
答案 2 :(得分:0)
这是怎么回事。
首先,让我们使用一些变体; bool
,int
和float
即可。
typedef boost::variant<bool, int, float> variant_type;
然后是基类,或多或少,就像你拥有它一样。
template
struct Visitor : public boost::static_visitor<>
{
void operator()(T0 value)
{
Process(value);
}
void operator()(T1 value)
{
Process(value);
}
void operator()(T2 value)
{
Process(value);
}
virtual void Process(T0 val){ std::cout << "I am Visitor at T0" << std::endl; }
virtual void Process(T1 val){ std::cout << "I am Visitor at T1" << std::endl; }
virtual void Process(T2 val){ std::cout << "I am Visitor at T2" << std::endl; }
protected:
Visitor(){}
};
接下来,我们有两个具体的变体。
template
struct Visitor1 : public Visitor
{
void Process(T0 val){ std::cout << "I am Visitor1 at T0" << std::endl; }
void Process(T2 val){ std::cout << "I am Visitor1 at T2" << std::endl; }
};
template
struct Visitor2 : public Visitor
{
void Process(T1 val){ std::cout << "I am Visitor2 at T1" << std::endl; }
void Process(T2 val){ std::cout << "I am Visitor2 at T2" << std::endl; }
};
最后,我们可以制作一个不同变体的单一载体:
int main() {
variant_type data(1.0f);
std::vector*> v;
v.push_back(new Visitor1());
v.push_back(new Visitor2());
apply_visitor(*v[0],data);
apply_visitor(*v[1],data);
data = true;
apply_visitor(*v[0],data);
apply_visitor(*v[1],data);
return 0;
}
这是输出:
I am Visitor1 at T2 I am Visitor2 at T2 I am Visitor1 at T0 I am Visitor at T0
如果出于某种原因我需要在一个容器中使用不同的变体,我肯定会考虑这个解决方案。我还想到将访问者粘贴到另一个变种中会更糟/更好。使用继承的好处在于它是可扩展的事实:你总是可以从一个类继承,但是一旦设置了一个变体,你就无法在不实际触及现有代码的情况下进行更改。