How can I use the boost visitor concept with a class containing state variables?

偶尔善良 提交于 2019-12-11 09:29:56

问题


I'm attempting to use boost::static_visitor to implement actions on a boost::variant type that affect the state of some variable. My approach was to contain all of the state variables in my command visitor class, but it seems this is not possible.

Here is my code example:

#include <string>
#include <sstream>
#include <vector>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>


struct TypeA
{
    int varA;
    int varB;
};

struct TypeB
{
   std::string varA;
   std::string varB;
};

typedef boost::variant<TypeA, TypeB> MyVariantType;

class MyCommandVisitor : public boost::static_visitor<>
{
public:
//These are just examples, the actions only need to be able to touch
// internal variables.
void operator()(TypeA & t) const
{
   m_runningSum += t.varA;
   m_outStream << "TYPEA ACTION: " << t.varB << std::endl;
}

void operator(TypeB & t) const
{
   m_charCount += t.varA.size();
   m_outStream << t.varB <<  " ACTION " << t.varA << std::endl;
}

std::string emitWork(std::vector<MyVariantType> listOfVariants)
{
    m_outStream.clear();
    m_runningSum = 0;
    m_charCount = 0;
    BOOST_FOREACH(MyVariantType & v, listOfVariants)
    {
        boost::apply_visitor(*this, v);
    }
    return m_outStream.str();
}

protected:
int m_runningSum;
int m_charCount;
std::stringstream outStream;
}; //End class MyCommandVisitor


int main(int argc, char **argv)
{
    TypeA ta;
    ta.varA = 1;
    ta.varB = 2; 

    TypeB tb;
    tb.varA = "String1";
    tb.varB = "String2";
    std::vector<MyVariantType> listOfWork;
    listOfWork.push_back(ta);
    listOfWork.push_back(tb);
    MyCommandVisitor myCV;

    std::string result = myCV.emitWork(listOfWork);

    std::cout << "Result:\n" << result << std::endl << std::endl;
    return 0;
}

I hope this snippet gets across the gist of what I'm trying to accomplish. It won't compile, however, giving the [paraphrased] error:

error: no operator "<<" matches these operands
   operand types are: const std::stringstream << const char [N]
m_outStream << "TYPE A ACTION: " << t.varB << std::endl;
             ^

I'm assuming this error is due to the const modifier that must be placed on the end of the operator() function prototype which makes the compiler believe that member variables cannot be modified by the function.

My question is thus:

What is the proper way to accomplish the visitor pattern (using boost::variant) with variables that must maintain state between visits?


回答1:


There were a couple of typos, but I made a few mods and it works now. Essentially your static_visitor class is mutating itself on each visit, so the operator() methods can't be const.

#include <string>
#include <sstream>
#include <vector>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>
#include <iostream>


struct TypeA
{
    int varA;
    int varB;
};

struct TypeB
{
   std::string varA;
   std::string varB;
};

typedef boost::variant<TypeA, TypeB> MyVariantType;

class MyCommandVisitor : public boost::static_visitor<>
{
public:
//These are just examples, the actions only need to be able to touch
// internal variables.
void operator()(TypeA & t)
{
   m_runningSum += t.varA;
   m_outStream << "TYPEA ACTION: " << t.varB << std::endl;
}

void operator()(TypeB & t)
{
   m_charCount += t.varA.size();
   m_outStream << t.varB <<  " ACTION " << t.varA << std::endl;
}

std::string emitWork(std::vector<MyVariantType> listOfVariants)
{
    m_outStream.clear();
    m_runningSum = 0;
    m_charCount = 0;
    BOOST_FOREACH(MyVariantType & v, listOfVariants)
    {
        boost::apply_visitor(*this, v);
    }
    return m_outStream.str();
}

protected:
int m_runningSum;
int m_charCount;
std::stringstream m_outStream;
}; //End class MyCommandVisitor


int main(int argc, char **argv)
{
    TypeA ta;
    ta.varA = 1;
    ta.varB = 2; 

    TypeB tb;
    tb.varA = "String1";
    tb.varB = "String2";
    std::vector<MyVariantType> listOfWork;
    listOfWork.push_back(ta);
    listOfWork.push_back(tb);
    MyCommandVisitor myCV;

    std::string result = myCV.emitWork(listOfWork);

    std::cout << "Result:\n" << result << std::endl << std::endl;
    return 0;
}

running on http://www.compileonline.com/compile_cpp11_online.php gives:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
Result:
TYPEA ACTION: 2
String2 ACTION String1



回答2:


I'd personally favour making the functor const. Instead, I like to bind the functor arguments to references:

static std::string emitWork(std::vector<MyVariantType> const listOfVariants) {
    int sum = 0, charCount = 0;
    std::stringstream os;
    BOOST_FOREACH(MyVariantType const& v, listOfVariants) {
        boost::apply_visitor(
            boost::bind(MyCommandVisitor(), _1, boost::ref(os), boost::ref(sum), boost::ref(charCount)), 
            v);
    }
    return os.str();
}

Note that

  • emitWork can now be static, reentrant etc.
  • the operator() can now be const

The rest of the visitor would look like this:

struct MyCommandVisitor : boost::static_visitor<> {
    void operator()(TypeA const& t, std::stringstream& os, int& sum, int& /*charCount*/) const {
        sum += t.varA;
        os << "TYPEA ACTION: " << t.varB << std::endl;
    }

    void operator()(TypeB const& t, std::stringstream& os, int& /*sum*/, int& charCount) const {
        charCount += t.varA.size();
        os << t.varB << " ACTION " << t.varA << std::endl;
    }
};

See it Live On Coliru



来源:https://stackoverflow.com/questions/23432606/how-can-i-use-the-boost-visitor-concept-with-a-class-containing-state-variables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!