Re: Differentiating inherited objects
Quote:
Originally Posted by
JovianGhost
I don't see the advantage in that; it still looks like I have to expand the Visitor_1 class every time I need a new subtype. Or maybe I'm just not understanding the concept.
How many places in your code do you need to inform it of the new class with patterns such as the visitor pattern? One? Two? or everywhere where you are determining the object? If it's one or two places, and then the rest of the code works, then that is much better than going through 10, 20, 30, or more places in your code where you have if() or switch() statements checking the object type. Somewhere at some point, your program has to be informed of the new class, so it has to be done in some organized, least error-prone way, and the visitor pattern is one of those ways to do it.
The visitor pattern, plus other patterns takes discipline in that you have to know where to add the functions or objects to the code, but once you do that, everything works. You aren't scrambling doing searches in your code for if() / switch() statements, making sure that each and every one knows about your new class.
Regards,
Paul McKenzie
Re: Differentiating inherited objects
Quote:
Originally Posted by
JovianGhost
I don't see the advantage in that; it still looks like I have to expand the Visitor_1 class every time I need a new subtype. Or maybe I'm just not understanding the concept.
It's a huge difference.
In your current code there may be lots of different places where you check the subtype of Base objects in order to make special provisions for each. You also have to make a not typesafe downcast from Base to be able to use the subtype object you've identified.
Using the Visitor pattern you change in one place only. You add a virtual visit method to the Visitor interface and then you supply a concrete visit method in the concrete sublclasses of Visitor, like Visitor_1, Visitor_2 etcetera. The compiler will help you ensure the design is consistent. If you "forget" to add a concrete visit method it will tell you. And furthermore, there's no unsafe downcasting involved at all.
So if you really need to identify subtypes (always first try to avoid that), Visitor is the least error-prone solution by far. It's also an established design patterns so other people should have no problem understanding what's happening.
Re: Differentiating inherited objects
BTW, if, as you stated in post #8, the only reason you need a polymorphic hierarchy is to setup the conversion functions then you could directly resolve the conversion paths at compile time through the approach Lindley suggested;
so, let's say you have a set of types A1,...,AN and a set of "basic" conversion functions { AjToAk for some j,k }; your problem is to automatically generate the complete set of conversion functions { AjToAk for every j,k } from compositions of the basic conversion ( if they exist, of course ).
Now, this is something I always wanted to do myself ( but never had the will :) ), so I took this opportunity to write some experimental code, that, if you like, you could test for me :)
here is the usage
Code:
// the class set
class A1{};
class A2{};
class A3{};
// the conversions set
typedef mpl::vector< Conv<A1,A2>, Conv<A2,A1>, Conv<A2,A3>, Conv<A3,A2> > MyConvertersSet;
// the basic conversions
void Convert( const A1&, A2&, MyConvertersSet ){ /*whatever*/ }
void Convert( const A2&, A1&, MyConvertersSet ){ /*whatever*/ }
void Convert( const A2&, A3&, MyConvertersSet ){ /*whatever*/ }
void Convert( const A3&, A2&, MyConvertersSet ){ /*whatever*/ }
int main()
{
A1 a1;
A2 a2;
A3 a3;
Convert( a1, a2, MyConvertersSet() );
Convert( a2, a3, MyConvertersSet() );
Convert( a1, a3, MyConvertersSet() ); // auto generated !
Convert( a3, a1, MyConvertersSet() ); // auto generated !
}
and the template machinery to achieve this
Code:
#include <boost/type_traits.hpp>
#include <boost/mpl/logical.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/back_inserter.hpp>
#include <boost/mpl/filter_view.hpp>
#include <boost/mpl/tranform_view.hpp>
#include <boost/mpl/tranform.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/equal.hpp>
namespace mpl = boost::mpl;
template <class SourceT, class SinkT>
struct Conv
{
typedef SourceT source_type;
typedef SinkT sink_type;
};
template <class ConvT>
struct GetSink
{
typedef typename ConvT::sink_type type;
};
template <class ConvT,class SourceT>
struct HasSource
{
typedef typename boost::is_same< SourceT, typename ConvT::source_type >::type type;
};
template <class PathT,class SinkT>
struct HasSink
{
typedef typename boost::is_same< SinkT, typename mpl::back<PathT>::type >::type type;
};
template <class T, class ConvertersSet>
struct Propagate
{
typedef typename mpl::filter_view< ConvertersSet, HasSource< mpl::_, T > >::type filtered_convs;
typedef typename mpl::transform_view< filtered_convs, GetSink<mpl::_> >::type type;
};
template <class PackedPathT>
struct UnPack
{
typedef typename mpl::pop_back< PackedPathT >::type prev_path;
typedef typename mpl::filter_view<
typename mpl::back<PackedPathT>::type,
mpl::not_< mpl::contains< prev_path, mpl::_ > >
>::type filtered_paths;
typedef typename mpl::fold< filtered_paths, mpl::vector<>,
mpl::push_back< mpl::_1,
mpl::push_back< prev_path, mpl::_2 >
> >::type type;
};
template <class PathsT, class ConvertersSet>
struct PropagatePaths
{
typedef typename mpl::transform< PathsT, mpl::push_back< mpl::_, Propagate< mpl::back<mpl::_ >, ConvertersSet >
> >::type packed;
typedef typename mpl::fold< packed, mpl::vector<>,
mpl::joint_view< mpl::_1, UnPack<mpl::_2> >
>::type unpacked;
typedef typename mpl::copy< unpacked, mpl::back_inserter< mpl::vector<> > >::type type;
BOOST_MPL_ASSERT_NOT(( mpl::empty<type> )); // if empty, no path exists
};
template <class PathT>
struct Match
{
typedef PathT type;
};
template <class PathsT, class SinkT>
struct Resolve
{
typedef typename mpl::find_if< PathsT, HasSink< mpl::_, SinkT > >::type match;
typedef typename mpl::if_< boost::is_same< match, typename mpl::end<PathsT>::type >,
PathsT,
Match< typename mpl::deref<match>::type >
>::type type;
};
template < class SourceT, class SinkT, class ConvertersSet >
void Convert( const SourceT& src, SinkT& snk, ConvertersSet set )
{
typedef mpl::vector< mpl::vector<SourceT> >::type StartPathsT;
typedef typename PropagatePaths< StartPathsT, MyConvertersSet >::type NextPathsT;
Convert( src, snk, set, NextPathsT() );
}
template < class SourceT, class SinkT, class ConvertersSet, class PathsT >
void Convert( const SourceT& src, SinkT& snk, ConvertersSet set, PathsT )
{
typedef typename PropagatePaths< PathsT, MyConvertersSet >::type NextPathsT;
Convert( src, snk, set, Resolve< NextPathsT, SinkT >::type() );
}
template < class SourceT, class SinkT, class ConvertersSet, class PathT >
void Convert( const SourceT& src, SinkT& snk, ConvertersSet set, Match<PathT> )
{
// ok, we have a path from SourceT to SinkT !
typedef typename mpl::pop_front<PathT>::type NextPathT;
typedef typename mpl::at_c<PathT,1>::type IntermidiateT;
IntermidiateT intermidiate;
Convert( src, intermidiate, set );
Convert( intermidiate, snk, set, Match<NextPathT>() );
}
template < class SourceT, class SinkT, class ConvertersSet >
void Convert( const SourceT&, SinkT&, ConvertersSet, Match< mpl::vector1<SinkT> > )
{
// do nothing
}
it should work for any conversion set and give an error if no path is found ...
it compiles and works ok in my system (VC++2008), although I'm not sure I've included all the necessary MPL headers ... ; moreoever, I've noticed that VC sometimes does not ask for "typename" disambiguators where they should be, so, you've been warned ... :)