шаблон посетителя для повышения :: вариант - PullRequest
10 голосов
/ 31 августа 2010

Я хотел бы использовать boost.variant<T0,T1,T2> в качестве параметра для класса шаблона «Посетитель», который предоставил бы операторы посетителей, как того требует механизм посетителей boost.variant, в этом случае все возвращают void, то есть

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

Шаблон также будет иметь для каждого из типов T0 ... в варианте соответствующую виртуальную функцию, которая по умолчанию ничего не делает.Пользователь может наследовать от класса шаблона и переопределять только те виртуальные функции, которые ему интересны. Это что-то похожее на хорошо известный паттерн «Шаблонный метод».Единственное решение, которое мне удалось найти, - это обернуть boost :: variable и связанного с ним посетителя в один шаблон и получить к ним доступ через 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 напрямую для создания экземпляра класса посетителя шаблона.Я посмотрел на использование параметров типа, нетиповых параметров и параметров шаблона шаблона, но, похоже, ничего не дает о себе знать.Что я пытаюсь сделать не возможно?Возможно, я что-то упустил, и был бы признателен, если бы кто-нибудь высказался по этому поводу.

Ответы [ 3 ]

15 голосов
/ 01 сентября 2010

Код с Boost Variant и виртуальной диспетчеризацией немного подозрительный. Особенно с учетом того, что вы знаете, что вас интересует в обработке во время компиляции, и нет абсолютно никакой необходимости создавать виртуальную таблицу во время выполнения для достижения ваших целей.

Я бы порекомендовал вам использовать частичную специализацию шаблонов . Таким образом, есть метод шаблона по умолчанию, который может принимать любой тип в варианте и ничего не будет делать. Для тех типов, которые вас интересуют, просто специализируйте шаблон.

Вот пример. У нас есть три типа - Foo, Bar и War. Нас интересуют только последние два типа и есть специализация для них. Так что Фу игнорируется.

#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 голос
/ 18 июля 2017

Со временем развиваются новые и интересные библиотеки.Этот вопрос старый, но с тех пор существует решение, которое лично для меня намного превосходит то, которое было дано до сих пор.

Отличная библиотека 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
}

Я работаю с ним сейчас, и пока им пользоваться очень приятно.

0 голосов
/ 10 марта 2011

Том, я считаю, что ваш вопрос имеет большой смысл в определенном контексте. Скажем, вы хотите хранить посетителей разных типов в векторе, но не можете, потому что все они разных типов. У вас есть несколько вариантов: снова использовать вариант для хранения посетителей, использовать boost.any или использовать виртуальные функции. Я думаю, что виртуальные функции - это элегантное решение, но не единственное.

Вот как это происходит.

Сначала давайте воспользуемся некоторым вариантом; bool, int и float подойдут.

typedef boost::variant<bool, int, float> variant_type;

Затем идет базовый класс, более или менее такой, каким он был у вас.

<code>
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(){}
};

Далее у нас есть два конкретных варианта.

<code>
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; }
};</p>

<p>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; }
};

Наконец, мы можем сделать один вектор из разных вариантов:

<code>
int main() {
  variant_type data(1.0f);
  std::vector*> v;
  v.push_back(new Visitor1());
  v.push_back(new Visitor2());</p>

<p>apply_visitor(*v[0],data);
  apply_visitor(*v[1],data);
  data = true;
  apply_visitor(*v[0],data);
  apply_visitor(*v[1],data);</p>

<p>return 0;
}

А вот и вывод:

I am Visitor1 at T2
I am Visitor2 at T2
I am Visitor1 at T0
I am Visitor at T0

Если бы по какой-то причине мне нужно было иметь разные варианты в одном контейнере, я бы наверняка рассмотрел это решение. Я также подумал бы, насколько хуже / лучше было бы на самом деле придерживаться посетителей в другом варианте. Хорошая особенность использования наследования заключается в том, что он расширяемый постфактум: вы всегда можете наследовать от класса, но как только вариант задан, вы не можете изменить его, фактически не затрагивая существующий код.

...