C++ visitor pattern handling templated string types?
I'm trying to use the visitor pattern to serialize the contents of objects. However one snag I'm hitting is when I'm visiting strings. My strings are of a templated type, similar to STL's basi开发者_如何学运维c_string. So something like:
basic_string<char_type, memory_allocator, other_possible_stuff> \\ many variations possible!
Since I can have very many different templated string types, I can't go and add them to my visitor interface. It would be ridiculous. But I can't add templates to my VisitString method because C++ prevents using templates parameters in virtual methods.
So what are my options to work around this?
EDIT: I've added some basic code
class IVisitor
{
public:
virtual void VisitString(some_kind_of_string_type string) = 0; // this is what I want in theory
};
class MyObject
{
public:
typedef basic_string<char8, myAllocator, some_flag> MyStringType;
Accept(IVisitor* visitor)
{
visitor->VisitString(mString);
}
private:
MyStringType string;
};
class MyOtherObject
{
public:
typedef basic_string<char16, myOtherAllocator, some_other_flag> MyOtherStringType;
Accept(IVisitor* visitor)
{
visitor->VisitString(mString);
}
private:
MyOtherStringType string;
};
class Reader : public IVisitor
{
public:
virtual void VisitString(some_kind_of_string_type string)
{
// read some data, give it to the string
}
}
Do you need runtime polymorphism?
struct object {
template <typename Visitor>
void accept( Visitor & v )
{
v( x );
v( a );
}
int x;
std::string a;
};
struct complex_object {
template <typename Visitor>
void accept( Visitor & v ) {
v( i );
o.accept(v); // [1]
}
int i;
object1 o;
};
struct DumpScreenVisitor {
void operator()( int x ) { std::cout << x << std::endl; }
template <typename char_t, typename traits_t, typename alloc_t>
void operator()( std::basic_string<char_t, traits_t, alloc_t> const & str )
{
std::cout << str << std::endl;
}
};
The call in [1] can be converted into v( o )
with a generic templated operator()
in the visitors that is the least specialized:
template <typename O>
void DumpScreenVisitor::operator()( O & o )
{
o.accept( *this );
}
But this can interfece with other visitor implementations (for example, the above visitor can be implemented with a single templated method):
struct DumpScreenVisitor {
template <typename T>
void operator()( T const & t ) {
std::cout << t << std::endl;
}
};
So at the end you will have to compromise in either way.
This approach is similar to the boost::variant visitor implementation (you may want to take a look at it), with the difference that the boost::variant is a single class and not a hierarchy.
In the end, I went with a slightly different approach. Instead of hoping to use a visitor with templated methods (which is, of course, impossible), I decided to pass a visitor-like class as a template parameter to my object's visit method. Totally simplified example:
class SomeKindOfVisitor // doesn't need to derive from a base class.
{
template <class StringClass>
void VisitString(StringClass& string) // I get to keep templated methods
}
class MyObject
{
typedef basic_string<char8, myAllocator, some_flag> MyStringType;
public:
template <class VisitorClass>
void Accept(VisitorClass& visitor)
{
vistior.VisitString<MyStringType>(mMyString);
}
private:
MyStringType mMyString;
}
With this method, I still get to use my templated strings while still being able to pass any kind of "visitor" to my objects.
your visitor should handle only a basic representation of strings (char* / wchar*);
it is then up to the accept method to process the cast.
Well, the question is, of the template parameters on your string can be so different, can you apply one single serialization method for them? If so, you could write an adapter that has a templated constructor that extracts all the information needed for serialization into a uniform representation. Then you visit the serializer with the adapter.
EDIT: After you added you code, I still think that an adapter could solve your problem, only the other way around. In you Accept
-method, construct a local adapter and pass it to the Visitor
. When the Visitor
has modified it, you can use a template method extractToString
on the adapter that converts the information to a specific string version. This may make the adapter quit complex, depending on how different the string-template instantiations have to be handled.
Since all your string classes are of different types, you will need some level of compromise (either a common sub-type, with virtual methods, for your strings, or an adapter, or adding a method for each different type to the visitor). Mixing generic-programming and oo can be a pain, especially if you don't accept compromises.
Eg.
class string_tag { /* common visitor interface */ };
template<typename char_t, ...> class basic_string : public string_tag {};
class IVisitor
{
public:
virtual void VisitString(string_tag& string) = 0; // this is what I want in theory
};
class MyObject
{
public:
typedef basic_string<char8, myAllocator, some_flag> MyStringType;
Accept(IVisitor* visitor)
{
visitor->VisitString(string);
}
private:
MyStringType string;
};
class MyOtherObject
{
public:
typedef basic_string<char16, myOtherAllocator, some_other_flag> MyOtherStringType;
Accept(IVisitor* visitor)
{
visitor->VisitString(string);
}
private:
MyOtherStringType string;
};
class Reader : public IVisitor
{
public:
virtual void VisitString(string_tag& string)
{
// read some data, give it to the string
}
}
May be you can consider below, but in this case you need to separate visitor mechanisms to different visitor classes. WStringVisitor and StringVisitor are just examples for different Visitor semantics.
#include <string>
#include <iostream>
using namespace std;
template <typename stringType>
class IVisitor{
public:
virtual void visit(stringType _string)=0;
};
class StringVisitor: public IVisitor<string>{
public:
void visit(string str){
cout<<"This is std::string implementation: "<< str << endl;
}
};
class WStringVisitor: public IVisitor<basic_string<wchar_t>>{
public:
void visit(basic_string<wchar_t> str){
//wprintf(L"This wide implementation : %S", str.c_str());
wcout<<"This is WString Visitor: "<< str << endl;
}
};
class MyObject{
public:
typedef basic_string<char> MyStringType;
void accept(IVisitor<MyStringType>& visitor){
visitor.visit("TEST STRING");
}
};
class MyOtherObject
{
public:
typedef basic_string<wchar_t> MyOtherStringType;
void accept(IVisitor<MyOtherStringType>& visitor)
{
visitor.visit(L"TEST WSTRING");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
MyObject acceptor;
MyOtherObject otheracceptor;
StringVisitor visitor;
WStringVisitor wvisitor;
acceptor.accept(visitor);
//otheracceptor.accept(visitor); compile error
otheracceptor.accept(wvisitor);
return 0;
}
I think the fundamental problem here is that the Visitor pattern is all about virtual functions, while you herd your strings through function templates. And these just don't easily mix. In fact, the only way I can think of to mix the two is type erasure.
If you don't find a way to do what you want using this technique, I don't think you'll find a way.
精彩评论