Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top