10

我想使用 aboost.variant<T0,T1,T2>作为模板“访问者”类的参数,该类将根据 boost.variant 访问者机制的要求提供访问者运算符,在这种情况下,所有返回 void 即,

void operator()(T0 value);
void operator()(T1 value);
void operator()(T2 value);

该模板还将为每个类型 T0... 在变体中具有相应的虚函数,默认情况下该虚函数不执行任何操作。用户可以从模板类继承并只重新定义他感兴趣的那些虚函数。这类似于众所周知的“模板方法”模式。我能想出的唯一解决方案是将 boost::variant 和关联的访问者包装在一个模板中,并通过 typedefs 访问它们。这工作正常,但感觉有点笨拙。这是代码:

#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 来实例化模板访问者类。我看过使用类型参数、非类型参数和模板模板参数,但似乎没有任何建议。我想做的事是不可能的吗?我可能会遗漏一些东西,如果有人对此有任何意见,我将不胜感激。

4

3 回答 3

16

带有 Boost Variant 和虚拟调度的代码有点可疑。特别是考虑到您知道在编译时对处理什么感兴趣,并且绝对不需要在运行时创建虚拟表来实现您的目标。

我建议您使用部分模板专业化。因此,有一个默认模板方法可以接受变体中的任何类型并且什么都不做。对于您感兴趣的那些类型,只需专门化模板即可。

这是一个例子。我们有三种类型 - 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);
}
于 2010-08-31T20:37:39.950 回答
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
}

我现在正在使用它,到目前为止,使用它真的很愉快。

于 2017-07-18T13:29:12.587 回答
0

汤姆,我相信你的问题在特定情况下很有意义。假设您想在一个向量中存储多种类型的访问者,但您不能,因为它们都是不同的类型。您有几个选择:再次使用 variant 来存储访问者、使用 boost.any 或使用虚拟函数。我认为虚函数在这里是一个优雅的解决方案,但肯定不是唯一的。

这是怎么回事。

首先,让我们使用一些变体;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; }

这是输出:

我是 T2 的访客 1
我是 T2 的访客 2
我是 T0 的访客 1
我是T0的访客

如果出于某种原因我需要在一个容器中包含不同的变体,我肯定会考虑这个解决方案。我还认为实际上将访问者吸引到另一个变体中会更糟/更好。使用继承的好处是它是事后可扩展的:你总是可以从一个类继承,但是一旦设置了一个变体,你就不能在不实际接触现有代码的情况下更改它。

于 2011-03-10T20:39:58.817 回答