Visitor pattern using boost::bind & overloaded functions
I'm trying to add a Visitor pattern to my code and want to keep this as general as possible. More specificly, I'd like not having to hardcode the callback function into my accept
function. So, as a parameter to the accept
function, I give a boost::function
object, which is then called by the visited object.
My problem is, however, that I can't bind to overloaded functions (because boost::bind doesn't know which exact function to bind to) and I can't cast the overloaded function to the correct one, because I don't know the exact type of the visited class (this is important).
Is there any way to create what I want? I searched SO, but found only questions about how to fix the bind issue (which is by casting, which is someth开发者_运维知识库ing I can't do).
Below is some code that does not compile, but shows what I'd like to archieve:
#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>
struct A
{
virtual void acceptVisitor (boost::function<void (A const &)> callbackFnc)
{
callbackFnc(*this);
}
};
struct B : virtual public A {};
std::string printMe (A const & a) { return "A"; }
std::string printMe(B const & a) { return "B"; }
int main()
{
std::vector<std::string> stringVector;
boost::function<void (A const &)> bindedFnc = boost::bind(&std::vector<std::string>::push_back,
&stringVector, boost::bind(&printMe, _1));
A A1;
B A2;
A1.acceptVisitor(bindedFnc);
A2.acceptVisitor(bindedFnc);
}
[Edit] Fixed example code, because previous version (as ildjarn remarked) didn't actually call the accept
function.
This should get you halfway there. It compiles with Visual C++ 2010 and g++ 4.5.1 using Boost 1.46.0. It does not compile with the Visual C++ 2010 C++0x <functional>
implementation; I'm not yet sure why.
The setup:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>
// This helper allows you to do a push_back in a bind; you can't bind
// directly to std::vector::push_back because the type of a Standard
// Library member function is unspecified.
struct do_push_back
{
typedef void result_type;
template <typename TSequence, typename TElement>
void operator()(TSequence* sequence, const TElement& element) const
{
sequence->push_back(element);
}
};
The demonstration:
// Class hierarchy for demonstration:
struct B { };
struct D : B { };
// Instead of using overlodaed nonmember functions, you can overload
// operator() in a function object. This allows you to bind to an
// instance of this function object, not directly to one of the overloads.
struct make_string
{
typedef std::string result_type;
std::string operator()(const B&) const { return "B"; }
std::string operator()(const D&) const { return "D"; }
};
int main()
{
std::vector<std::string> strings;
// Note that we do not use a boost::function here:
auto f = boost::bind(do_push_back(),
&strings,
boost::bind(make_string(), _1));
// Call our 'f' with B and D objects:
f(B());
f(D());
std::copy(strings.begin(), strings.end(),
std::ostream_iterator<std::string>(std::cout));
}
The result:
BD
Here's why this is only half of the solution: you can't store the result of the call to boost::bind
in a boost::function
. The problem is that when you use a boost::function<void(const B&)>
to store the bound function object, it will always pass a const A&
as an argument to the bound function.
Even if you call the boost::function
object with a D
argument, it gets converted to a const B&
. A lot of type information is lost when you use a boost::function
; this loss of type information is necessary to make boost::function
usable as a generic callable object container.
That doesn't mean that you can't pass the bound function object around, though; you just have to use templates to prevent type information from getting lost:
template <typename TFunction>
void test(std::vector<std::string>& strings, TFunction f)
{
f(B());
f(D());
}
// In main():
test(strings, f);
// Or, if you don't have C++0x's "auto", you can pass the bound
// function object directly:
test(strings, boost::bind(do_push_back(),
&strings,
boost::bind(make_string(), _1)));
It is unfortunate that in order not to lose type information you have to pass the bound function object to a function template. It means that your idea of making acceptVisitor
a virtual member function won't work with this solution (it isn't possible to have a virtual function template).
In any case, hopefully this is helpful to you.
Perhaps you will find the generic Visitor template from the Loki library useful
See http://loki-lib.sourceforge.net
#include <iostream>
#include <Loki/Visitor.h>
struct Animal : public Loki::BaseVisitable<void, Loki::DefaultCatchAll, false>
{
};
struct Cat : public Animal
{
LOKI_DEFINE_VISITABLE();
};
struct Dog : public Animal
{
LOKI_DEFINE_VISITABLE();
};
struct Flower : public Loki::BaseVisitable<void, Loki::DefaultCatchAll, false>
{
};
struct Tulip : public Flower
{
LOKI_DEFINE_VISITABLE();
};
struct AnimalAndFlowerVisitor
: public Loki::BaseVisitor
, public Loki::Visitor<Cat, void, false>
, public Loki::Visitor<Dog, void, false>
, public Loki::Visitor<Tulip, void, false>
{
void Visit(Dog & dog)
{
std::cout << "Do something with the dog\n";
}
void Visit(Cat & cat)
{
std::cout << "Do something with the cat\n";
}
void Visit(Tulip & tulip)
{
std::cout << "Do something with the tulip\n";
}
};
int main(int argc, char* argv[])
{
Dog dog;
Cat cat;
Tulip tulip;
Animal & animalDog = dog;
Flower & tulipFlower = tulip;
AnimalAndFlowerVisitor visitor;
animalDog.Accept(visitor); // will print "Do something with the dog"
tulipFlower.Accept(visitor); // will print "Do something with the tulip"
return 0;
}
精彩评论