10

をテンプレート 'Visitor' クラスのパラメーターとして使用したいと思いboost.variant<T0,T1,T2>ます。このクラスは、boost.variant ビジター メカニズムで必要なビジター オペレーターを提供します。この場合、すべて void を返します。

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

テンプレートには、バリアント内の各タイプ T0... に対して、デフォルトでは何もしない対応する仮想関数もあります。ユーザーはテンプレート クラスから継承し、関心のある仮想関数のみを再定義できます。これは、よく知られている「テンプレート メソッド」パターンに似たものです。私が思いついた唯一の解決策は、boost::variant と関連するビジターの両方を 1 つのテンプレートにラップし、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 を使用できるようにしたいと考えています。型パラメーター、非型パラメーター、およびテンプレート テンプレート パラメーターの使用について調べましたが、何も示唆していないようです。私がやろうとしていることは不可能ですか?私は何かが欠けているかもしれません。誰かがこれについて意見を持っていれば幸いです。

4

3 に答える 3

16

Boost Variant と仮想ディスパッチを使用したコードは少し怪しいです。特に、コンパイル時に何を処理したいかを知っていることを考慮に入れると、目標を達成するために実行時に仮想テーブルを作成する必要はまったくありません。

部分的なテンプレートの特殊化を使用することをお勧めします。したがって、バリアントの任意の型を受け入れることができ、何もしないデフォルトのテンプレート メソッドを用意します。興味のあるタイプについては、テンプレートを特化してください。

ここに例があります。Foo、Bar、War の 3 つのタイプがあります。最後の 2 つのタイプのみに関心があり、それらに特化しています。したがって、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!

ここに別の解決策があります。タイプリストで指定したものを除いて、すべてを無視するデフォルトの訪問者を作成します。型のリストを 2 回指定する必要があるため、あまり便利ではありません。1 回は型リストで、次に各処理メソッド (演算子) で指定します。さらに、実際には汎用テンプレートが訪問者を継承します。それにもかかわらず、ここに行きます:

#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ライブラリ。ユーリー・ソロドキー、ガブリエル・ドス・レイス、ビャルネ・ストロストラップ自身によって書かれています。この質問につまずいた人のために、README から抜粋した例を次に示します。

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

トム、あなたの質問は特定の文脈において非常に理にかなっていると思います。複数のタイプの訪問者をベクターに格納したいが、それらはすべて異なるタイプであるため、格納できないとします。いくつかの選択肢があります: 再度バリアントを使用して訪問者を保存するか、boost.any を使用するか、仮想関数を使用します。ここでは仮想関数が洗練されたソリューションだと思いますが、確かに唯一のものではありません。

これがその方法です。

まず、いくつかのバリアントを使用しましょう。boolint、そして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(){}
};

次に、2 つの特定のバリアントがあります。


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 の Visitor1 です
私は T2 の Visitor2 です
私は T0 の Visitor1 です
私は T0 の訪問者です

何らかの理由で 1 つのコンテナーに異なるバリアントを含める必要がある場合は、このソリューションを検討します。また、訪問者を実際に別のバリアントに固執することがどれほど悪い/良いかについても考えます。継承を使用することの良い点は、事後的に拡張可能であることです。いつでもクラスから継承できますが、バリアントが設定されると、実際に既存のコードに触れずに変更することはできません。

于 2011-03-10T20:39:58.817 に答える