Multimethods idioms and library support

L

Larry Evans

If g++ is available, you can use the -std=gnu++0x option to enable
variadic templates and then use the code demonstrated the
test driver here:

http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp
[snip]
So, after I managed to check out the code, it makes more sense.

But, tell me if the following is correct:
using this library on a hirarchy of classes and a certain multimethod.
- I have to collect the declarations of all different overrides in the
same functor class, like functor3 and functor_any do.
Yes.

- I have to collect all the concrete classes of the hirarchy in one
list, as under hosts_concrete<>

Yes. Otherwise, how could you define the abstract visitor class in an
extensible way?
If so, I think this API doesn't solve the main disadvantages I see
with the dynamic_cast idiom.


In my example it would look about:
* except I'm not sure what should be ResultType in there, becuase
hunt() returns void, but what happens if other multimethods return
other types?

I'm not sure. I remember finding, for some reason, the using
reifier_visitor was less flexible the reifier_switch for, IIRC, this
reason. I'll have to think more on this.
struct Carnivours_concrete
: mpl::package< Lion, Anaconda, Bear >
{
};


class Carnivour
{
typedef reifier_visit_abstract_seq< ResultType
, typename Carnivours_concrete::type>
visitor_abstract;

virtual ResultType accept( visitor_abstract const&)const=0;
};

class Lion: public Carnivour
{
...

ResultType accept( visitor_abstract const& a_visitor)const
{
return a_visitor.visit(*this);
}
};

class hunt_functor
{


void operator()( Lion const&, Gazelle& )
{
//jumps on it and bite its neck
}

//and same for:
void operator()( Lion const&, Girrafe& )
{
//bite its ass
}

void operator()( Anaconda const&, Gazelle& )
{
//inject venom

}

//Anaconda can't kill girrafes so no override for that one

void operator()( Bear const&, Prey& )
{
//because Bears catch everything in the same way lol.

}


};

itaj

Let me see if I can implement this with reifier_visitor.
I'll get back to you.

Thanks for the feedback!

-regards,
Larry
 
I

itaj sherman

If g++ is available, you can use the -std=gnu++0x option to enable
variadic templates and then use the code demonstrated the
test driver here:
http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp
[snip]
So, after I managed to check out the code, it makes more sense.
But, tell me if the following is correct:
using this library on a hirarchy of classes and a certain multimethod.
- I have to collect the declarations of all different overrides in the
same functor class, like functor3 and functor_any do.
Yes.

- I have to collect all the concrete classes of the hirarchy in one
list, as under hosts_concrete<>

Yes. Otherwise, how could you define the abstract visitor class in an
extensible way?

I suppose the general idea could maybe be to hold some static variable
of dispatch matrices and edit it in constructors of other static
variables.
But that is only good for pure runtime dispatch, from what I figured,
your library is about compile type dispatching, so maybe it doesn't
have enough in common with the solution I'm looking for. I'm not sure.
I'm not sure. I remember finding, for some reason, the using
reifier_visitor was less flexible the reifier_switch for, IIRC, this
reason. I'll have to think more on this.

Mainly I deduced my conclusion from the test code, seeing there how
you used reifier_visitor and reifier_apply.
I just gazed at the code of reifier_visitor, I don't know how it works
at all, neither the difference with reifier_switch.

itaj
 
L

Larry Evans

Is it supposed to be possible do checkout the whole svn tree?

I think so.
It keeps breaking.
Should I better take just part?

Yes, at least according to the following post:


http://article.gmane.org/gmane.comp.lib.boost.user/65939/match=using+sandbox+es
For compisite_storage you mean? Or certain part?

Well, now that you mention it, I'd have to provide documentation for
mpl, at least, and maybe some other variadic template libraries used
by composite storage.
What exactly is the name of
"variants"? Is it like number of virtual parameters for the
function?

I was unclear. If you have a types:

typedef variant<T11,T12,...,T1n_1> var_1;
typedef variant<T21,T22,...,T2n_2> var_2;
...
typedef variant<Tm1,Tm2,...,T1n_m> var_m;

and an overloaded function, f, taking m arguments where:

arg_1 can be one of the var_1 template args.
arg_2 can be one of the var_2 template args.
...
arg_m can be one of the var_m template args.

the the compile-time goes up proportional to

n_1*n_2*...*n_m

However, that's just extrapolating from the benchmark results where m
was only 2, but you can see from the curve fits that the time does go
up proportional to n_1*n_2.
One measure is the assignment operator.
What does the binary test do?

If you mean the TestVariantBinary.cpp in the sandbox, then it is the
test driver for comparing 3 methods for doing binary visitation:

boost::variant
boost/composite_storage/pack/multiple_dispatch
A specialization similar to boost::variant but optimized
for smart pointers. This was developed by Paul Bormans.
I know nothing about boost development, what does it measure?

I don't know about boost development, but the measurement done
by the Makefile in the variants_compare.zip in the vault is that done
by the g++ compiler when passed the -ftime-report option. More
precisely it measures the TOTAL time output by that report option.
Can you point me to some reading about it?

I'd just try using the -ftime-report option to see the output. I just
picked the TOTAL time part of the output because that seemed good
enough.
So one_of_multiple_dispatch is supposed to do what I want?

Yes, when using reifier_visitor as 1st arg to reify_apply.
What exactly is the relation of it with one_of_maybe?

I should clarify:

There are two methods used:

1) reifier_visitor
2) reifier_switch

Each method uses an overloaded function:

f(c_1,c_2,...,c_m)

where:

c_1 may be from one of a set of types, T11, T21,...,T1n_1
c_2 may be from one of a set of types, T21, T22,...,T2n_2
...
c_m may be from one of a set of types, Tm1, Tm2,...,T2n_m

IOW, there are n_1*n_2*....*n_m overloads of f.

The dispatching is done with a call such as:

reify_apply<reifier_xxx>(f,a_1,a_2,...,a_m)


where:
xxx = visitor or switch
a_1,a_2,...,a_m are abstract arguments which are "reified"
(turned into conrete arguments) by reifier_xxx.

The differences between the abstract types of these two methods is
described next.

1) reifier_visitor:

With virtual functions you have an inheritance hierarchy:

base_type
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
|/ \|/ \|
derive_1 derive_2 derive_3

and base_type has virtual function taking an abstract visitor:

struct abs_visitor
{
virtual void visit(derive_1&)=0;
virtual void visit(derive_2&)=0;
virtual void visit(derive_3&)=0;
};

and each derive_i has the concrete visit functions, reflecting the
standard visitor pattern:

http://www.dofactory.com/Patterns/PatternVisitor.aspx

The correspondence between the above inheritance diagram and the code
here:

http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp

is:

here code
---- ----
base_type template<typename ResultType>
struct host_abstract;
derived_I template<unsigned I,typename ResultType>
struct host_concrete;

The abstract types in this method are like base_type.
The concrete types are like derived_I.

2) reifier_switch

In contrast, with reifier_switch, you have a disjoint sum
data structure implemented using a tag or discriminator
and a set of "bounded"(the name used by boost::variant docs)
types. This type is declared as:

enum tag_type
{ tag_1
, tag_2
, tag_3
};
typedef
multiple_dispatch::container
< tags::eek:ne_of_maybe
, mpl::integral_c<tag_type,tag_1>//discriminator
, derive_1 //bound type corresponding to tag_1
, derive_2 //bound type corresponding to tag_2
, derive_3 //bound type corresponding to tag_3base_type;

The abstract types in this method are like base_type.
The concrete types are like derived_I.

Does the above make things clearer?
 
L

Larry Evans

On 02/22/11 16:08, Larry Evans wrote:
[snip]
Let me see if I can implement this with reifier_visitor.
I'll get back to you.
After some corrections to:

replace_source_with_target_ptr.hpp

got the attached to run and print:

Lion jumps on Girrafe and bites its ass.

However, when the #if on line 188 is switch, I get
very obscure compile-time error messages. So, it
can't handle concrete references. It apparently
can only handle abstract references.

I'll see if I can correct that.

-Larry
 
L

Larry Evans

On 02/22/11 16:08, Larry Evans wrote:
[snip]
Let me see if I can implement this with reifier_visitor.
I'll get back to you.
After some corrections to:

replace_source_with_target_ptr.hpp

got the attached to run and print:

Lion jumps on Girrafe and bites its ass.

However, when the #if on line 188 is switch, I get
very obscure compile-time error messages. So, it
can't handle concrete references. It apparently
can only handle abstract references.

I'll see if I can correct that.
The compiler error messages state that:

hosts_concrete<Lion>

is an incomplete type in reifier_visitor.hpp line 209.
This is where there's a hosts_concrete<HeadAbstract>
and HeadAbstract is Lion which is not abstract.
hosts_concrete is supposed to be specialized on
the abstract class to return the set of concrete
classes for that abstract class. Since Lion already
is an concrete class, there's no need to specialize
hosts_concrete on it; hence, the error.

Will think about solution.
 
L

ld

Is there any concensus idiom on how to code with multimethods?
How would you go about coding them today?

There's the multi-dispatch idiom: For each override type of the first
parameter. declare a different virtual function to dispatch the second
parameter.http://en.wikipedia.org/wiki/Multiple_dispatch
But it requires that the definition of every polymorphic class of one
parameter #includes the definition of all polymorphic types of the
following parameter. This makes the code messy, and not scalable due
to circular dependencies between modules.

I had to use them for a some medium size hirarchy and this idiom
became quite a bother. Not to mention that users coult not add their
types becuase this idiom is circular dependant.

I'm more interested in some library that would support this in a more
generic way.
I suppose any such library would need some compiler specific code for
each compiler that it supports, but the API should be the same.

BTW, Stroustrup in "The Design and Evolution of C++ Bjarne Stroustrup"/
13.8 comsiders adding multimethods as a c++ language favorable.
Although less important than many other features. And there are some
proposals for it. However, I don't know there's currently any point in
the future when it's predicted to be added. And a library solutions
seems good

itaj

If you can afford C instead of C++, you can read papers and use the
COS lib

http://cern.ch/laurent.deniau/html/cos-dls09-draft.pdf (short
version)
http://cern.ch/laurent.deniau/html/cos-oopsla09-draft.pdf (long
version)

regards,

Laurent.
 
I

itaj sherman

If you can afford C instead of C++, you can read papers and use the
COS lib

Well, that is basically my idea. Although when you work in C, the
language doesn't offer almost any of the features of this object
system (classes, inheritance, function resolution, templates?), so it
seems right to use it for everything. Unlike C, C++ has core features
for everything that this library does, except multimethods.
Principally I want to use the core features of C++ to write my code. I
only need a library that supports multimethods, that would extend them
over hirarchies that are already written with core C++ features. So in
that sense, I don't think I can afford transforming all my hirarchies
into any library. That would beat the purpose of finding the crispiest
idiom for multimethods.

itaj
 
I

itaj sherman


Well, I thought of a way to somewhat weaken that restriction.
Using partial specialization, it's possible to break the overrides to
difference locations, except that the invocations have to include the
declarations for them.

for example:

template< template< typename Args ... > Ftor >
class functor_forward
{
typedef Ftor::ResultType ResultType;

//data
private: Ftor ftor_;

//xtor
public:
template< Args1 ... >
functor_forward( Args1&& args )
:
ftor_( std::forward( args... ) )
{}

//methods
public: ResultType operator()(Args const&... args) const
{
return ftor_( args ... );
}
};


Then user code can write partial specializations:


template< typename ConcreteCarnivour, typename ConcretePrey >
class hunt_functor
{
};


class hunt_functor< Lion, Giraffe >
{
public: typedef void ResultType;

public: void operator()( Lion const& lion, Giraffe& giraffe ) const
{
//...
}
};

//and somewhere else in the code:
class hunt_functor< Bear, Prey >
{
....
};

//but invocation must include forward-dec for all specializations
pack::multiple_dispatch::reify_apply<
pack::multiple_dispatch::reifier_xxx >(
functor_forward< hunt_functor >(), carnivour, prey )


It's also possible to use function overload:

template< typename ConcreteCarnivour, typename ConcretePrey >
class hunt_functor
{
public: typedef void ResultType;

public: void operator()(
ConcreteCarnivour const& carnivour,
ConcretePrey& prey ) const
{
return hunt( carnivour, prey );
}
};


void hunt( Lion const&, Giraffe& );
void hunt( Bear cosnt&, Prey& );
//implementations wherever.

pack::multiple_dispatch::reify_apply<
pack::multiple_dispatch::reifier_xxx >(
functor_forward< hunt_functor >(), carnivour, prey )

itaj
 
J

James Kanze

Is there any concensus idiom on how to code with multimethods?
How would you go about coding them today?
There's the multi-dispatch idiom: For each override type of the first
parameter. declare a different virtual function to dispatch the second
parameter.http://en.wikipedia.org/wiki/Multiple_dispatch
But it requires that the definition of every polymorphic class of one
parameter #includes the definition of all polymorphic types of the
following parameter. This makes the code messy, and not scalable due
to circular dependencies between modules.

The classical solution is:

class Derived1;
class Derived2;
// ...

class Base
{
protected:
virtual void doSomething(Derived1&);
virtual void doSomething(Derived2&);
// ...
public:
virtual void doSomething(Base& other);
};

class Derived1 : public Base
{
protected:
virtual void doSomething(Derived1& other)
{
// Derived1, Derived1 handling...
}
virtual void doSomething(Derived2& other)
{
// Derived1, Derived2 handling...
}
public:
virtual void doSomething(Base& other)
{
other.doSomething(*this);
}
};

If the hierarchy is closed (all of the derived classes
identifiable, and managed by a single entity), and not too big,
this is fine. If you want to be able to easily expand the
hierarchy, then it is far from ideal, and anything over four or
five derived classes quickly becomes overwhelming.

Multiple dispatch on open or large hierarchies is problematic in
general. For n derived classes, you need n^2 functions
(regardless of how the dispatch works), and when you add
a derived class, you have to define its interactions with all of
the existing classes. It's best avoided, but it is possible to
make it work: you need something like a
std::map<std::pair<std::type_index, std::type_index>,void (*)
(Base&, Base&)>
, where void Base::doSomething(Base& other) isn't virtual, but
looks up the actual (free) function to call in the map. Getting
the map initialized with all of the necessary functions is
non-trivial, but if you can provide a default Base&, Base&
functionality, and only need a few specific overrides, it might
be OK.
I had to use them for a some medium size hirarchy and this
idiom became quite a bother. Not to mention that users coult
not add their types becuase this idiom is circular dependant.
I'm more interested in some library that would support this in
a more generic way. I suppose any such library would need
some compiler specific code for each compiler that it
supports, but the API should be the same.

If you want an open hierarchy which supports all possible
combinations, the problem isn't the compiler; it's how to
provide all of the necessary functions. Each new class has to
provide all of the functions for it and all of the existing
classes. This quickly gets out of hand.
 
L

Larry Evans

On 02/22/11 16:08, Larry Evans wrote:
[snip]
Let me see if I can implement this with reifier_visitor.
I'll get back to you.
After some corrections to:

replace_source_with_target_ptr.hpp

got the attached to run and print:

Lion jumps on Girrafe and bites its ass.

However, when the #if on line 188 is switch, I get
very obscure compile-time error messages. So, it
can't handle concrete references. It apparently
can only handle abstract references.

I'll see if I can correct that.
The compiler error messages state that:

hosts_concrete<Lion>

is an incomplete type in reifier_visitor.hpp line 209.
This is where there's a hosts_concrete<HeadAbstract>
and HeadAbstract is Lion which is not abstract.
hosts_concrete is supposed to be specialized on
the abstract class to return the set of concrete
classes for that abstract class. Since Lion already
is an concrete class, there's no need to specialize
hosts_concrete on it; hence, the error.

Will think about solution.
One solution is to define a new metafunction in reifier_visitor.hpp:

template
< typename HostConcretestruct host_abstract
/**@brief
* Metafunction returning Abstract class
* of which HostConcrete is a member.
* IOW, does inverse of hosts_concrete.
*/
{
typedef
HostConcrete
type
//By default, HostConcrete is member
//of a type hierarchy starting at
//HostConcrete.
;
};

and them modify the argument, HeadAbstract, to hosts_concrete to
super of reifier_visitor by host_abstract<HeadAbstract>::type.
That converts any concrete type back to its abstract super.
Of course that's less than ideal because it requires
specializations of host_abstract to map all the derived classes
back to their abstract classes and causes mover virtual
function calls to undo what was just done by host_abstract.

Will upload the modified reifier_visitor.hpp to sandbox.
The modified driver, with all the host_abstract specializations,
is attached.

-Larry
 
S

Stuart Redmann

You replace a dynamic_cast if-else-if list with a virtual function
Prey::dispatchMe. This save the detailing on 1 parameter. I'm not sure
it would be so helpful for 3 or more virtual parameters.
Also the more internal animal_kingdom module contains Prey which has
to know (forward-dec) the name of CarnivourDispatch which in turn has
to know all polymorphic types under Prey. As it is, it breaks the
encapsulation of the module, becuase user must make it somehow aware
of his different derivations.

There is no possible way that Double Dispatch could work without some
entity knowing all sub-classes that are involved in Double Dispatch.
The way I have shown is probably far from perfect, but it shows that
this problem can be solved by C++ without resorting to dynamic_casts.
I think that would be especially more
problematic if there was another user-code adding more derivations,
but that would want to use some of these derivations too.

Well, if two users A and B add different objects, someone has to
figure what should happen if objects from user A meet objects of user
B.


SCNR:
#include <iostream>

// We want to be able to dynamically dispatch three different
// base classes, Prey, Carnivour, and Location, with n
// sub-classes. Since Double Dispatch uses vtables, we have to
// have a class with n^2 entries in its vtable. This class contains
// the actual algorithmic part (in our scenario this class will
// be the Carnivour class).
// To be able to implement Double Dispatch, we have to stuff
// one additional class with a vtable of n entries (Location).

///////////////////////////////////////////////////////////

class CarnivourDispatch;
class Location;

class Prey
{
public:
virtual void dispatchMe (CarnivourDispatch*,Location&) = 0;
};

///////////////////////////////////////////////////////////

class Location;

class Carnivour
{
public:
virtual void hunt (Prey& prey, Location& location) = 0;
};

///////////////////////////////////////////////////////////

// CarnivourDispatch needs to know all derived classes, both
// prey and locations.
class Plains;
class Ocean;
class Gazelle;
class Giraffe;
class Gnu;

class CarnivourDispatch : public Carnivour
{
public:
// This starts the double dispatch.
virtual void hunt (Prey& prey, Location& location)
{
prey.dispatchMe (this, location);
}
virtual void huntPreyLocation (Gazelle&, Plains&) = 0;
virtual void huntPreyLocation (Giraffe&, Plains&) = 0;
virtual void huntPreyLocation (Gnu&, Plains&) = 0;
virtual void huntPreyLocation (Gazelle&, Ocean&) = 0;
virtual void huntPreyLocation (Giraffe&, Ocean&) = 0;
virtual void huntPreyLocation (Gnu&, Ocean&) = 0;
};

///////////////////////////////////////////////////////////

template<class t_ImplementationClass>
class PreyDispatchHelper : public Prey
{
public:
void dispatchMe (CarnivourDispatch* Hunter, Location& location)
{
location.dispatchMe (Hunter, *static_cast<t_ImplementationClass*>
(this));
}
};

class Giraffe : public PreyDispatchHelper<Giraffe>
{
};

class Gazelle : public PreyDispatchHelper<Gazelle>
{
};

class Gnu : public PreyDispatchHelper<Gnu>
{
};

//////////////////////////////////////////////////////////////////////////
// We need to know all derived classes of Prey at compile-time.

// Forward declaration of all Prey-derived classes.
class Gazelle;
class Giraffe;
class Gnu;

template<class t_Base1, class t_Base2>
class TMultipleInheritance : public t_Base1, public t_Base2
{
public:
typedef t_Base1 TBase1;
typedef t_Base2 TBase2;
};

class NullPrey
{};

class PreyEnum : public TMultipleInheritance
<
Gnu,
TMultipleInheritance
<
Giraffe,
TMultipleInheritance
<
Gazelle, NullPrey{
};

// LocationHelper2 adds the virtual methods
// virtual void dispatchMe (CarnivourDispatch*, Gazelle&) = 0;
// virtual void dispatchMe (CarnivourDispatch*, Gnu&) = 0;
// and so on to the Location interface.
template<class t_DummyArgument = int, class t_PreyEnum = PreyEnum>
class LocationHelper2 : public LocationHelper2<t_DummyArgument,
typename t_PreyEnum::TBase2>
{
public:
typedef LocationHelper2<t_DummyArgument, typename
t_PreyEnum::TBase2> TBaseLocationHelper2;
using TBaseLocationHelper2::dispatchMe;
virtual void dispatchMe (CarnivourDispatch* Hunter,typename
t_PreyEnum::TBase1& prey) = 0;
};

template<class t_DummyArgument>
class LocationHelper2<t_DummyArgument, NullPrey>
{
public:
typedef NullPrey TBaseLocationHelper2;

// We need this as base for the using-declaration in the
// non-specialized template.
void dispatchMe (){}
};

// We could as well drop this class, but it makes forward
// declarations much easier.
class Location : public LocationHelper2<>
{
};

////////////////////////////////////////////////////////////
// Implementation helper of the virtual methods of Location
template<class t_ImplemenationClass, class t_PreyEnum = PreyEnum>
class LocationDispatchHelper2 : public
LocationDispatchHelper2<t_ImplemenationClass, typename
t_PreyEnum::TBase2>
{
protected:
typedef LocationDispatchHelper2<t_ImplemenationClass, typename
t_PreyEnum::TBase2> TBaseLocationDispatchHelper2;
public:
using TBaseLocationDispatchHelper2::dispatchMe;
virtual void dispatchMe (CarnivourDispatch* Hunter,typename
t_PreyEnum::TBase1& prey)
{
Hunter->huntPreyLocation (prey,
*static_cast<t_ImplemenationClass*> (this));
}
};

template<class t_ImplemenationClass>
class LocationDispatchHelper2<t_ImplemenationClass, NullPrey> : public
Location
{
protected:
typedef Location TBaseLocationDispatchHelper2;
};


// Now we can easily add our locations.
class Plains : public LocationDispatchHelper2<Plains>
{
};

class Ocean : public LocationDispatchHelper2<Ocean>
{
};

class Lion: public CarnivourDispatch
{
protected:
virtual void huntPreyLocation (Gazelle& gazelle, Plains& plains)
{
std::cout << "Lion jumps on gazelle and bites its neck in the
plains." << std::endl;
}

virtual void huntPreyLocation (Giraffe& giraffe, Plains& plains)
{
std::cout << "Lion bites giraffe's ass in the plains" <<
std::endl;
}

virtual void huntPreyLocation (Gnu& gnu, Plains& plains)
{
std::cout << "Lion rides gnu to death in the plains" << std::endl;
}

virtual void huntPreyLocation (Gazelle&, Ocean&)
{
std::cout << "Lion cooks gazelle in Pacific Ocean." << std::endl;
}
virtual void huntPreyLocation (Giraffe&, Ocean&)
{
std::cout << "Lion runs over giraffe with jet skies." <<
std::endl;
}

virtual void huntPreyLocation (Gnu&, Ocean&)
{
std::cout << "Lion launches torpedo at gnu." << std::endl;
}
};


//main.cpp
int main()
{
Carnivour* carnivour = new Lion;
Location* location = new Plains;
Location* location2 = new Ocean;
Prey* prey1 = new Gazelle;
Prey* prey2 = new Giraffe;
Prey* prey3 = new Gnu;
carnivour->hunt (*prey1, *location);
carnivour->hunt (*prey2, *location);
carnivour->hunt (*prey3, *location);
carnivour->hunt (*prey1, *location2);
carnivour->hunt (*prey2, *location2);
carnivour->hunt (*prey3, *location2);

return 0;
}


Regards,
Stuart
 
I

itaj sherman

Multiple dispatch on open or large hierarchies is problematic in
general. For n derived classes, you need n^2 functions
(regardless of how the dispatch works), and when you add
a derived class, you have to define its interactions with all of
the existing classes. It's best avoided, but it is possible to
make it work: you need something like a
std::map<std::pair<std::type_index, std::type_index>,void (*)
(Base&, Base&)>
, where void Base::doSomething(Base& other) isn't virtual, but
looks up the actual (free) function to call in the map. Getting
the map initialized with all of the necessary functions is
non-trivial, but if you can provide a default Base&, Base&
functionality, and only need a few specific overrides, it might
be OK.
If you want an open hierarchy which supports all possible
combinations, the problem isn't the compiler; it's how to
provide all of the necessary functions. Each new class has to
provide all of the functions for it and all of the existing
classes. This quickly gets out of hand.


Ofcourse for k virtual parameters, the number of possible different
overrides is n^k. But so is the number of possible different static
overloads for a regular n-ary function in c++. But usually there are
some generalizations that are done on the possibilities that
practically cut them to around log(n)^k to n*log(n)^k sets and subsets
(just top of my head estimation). In both cases that should be done
using the hierarchy and templates.

* For conviniency I'll base on the hipothetic language feature. But
what I mean is that it should be possible to create a library that
would support the same general constructs, just instead of syntax
you'll have to define classes and static variable of the library type.

template< typename C, typename P >
hunt_impl( C const&, P& )
{
....
}

template< typename C, typename Horse1, typename Horse2 >
hunt_impl( C const& carnivour, Interbreed< Horse1, Horse2 >& prey )
{
...
}

void hunt( override< Bear > const& carnivour, override< Prey >& Prey )
{
return hunt_impl( carnivour, prey );
}

//the following syntax override 2 cases:
void hunt( override< Lion > const& carnivour, override< Giraffe,
Gazelle >& prey )
{
return hunt_impl( carnivour, prey );
}

void hunt( override< Anaconda > const& carnivour, override< Giraffe,
Gazelle >& prey )
{
return hunt_impl( carnivour, prey );
}


Now if anyone came to the conclusion that they really have to specify
all different combinations, in order to describe their problem
correctly, then so be it - such syntax can only make it easier, not
worse.

itaj
 
J

James Kanze

Ofcourse for k virtual parameters, the number of possible different
overrides is n^k. But so is the number of possible different static
overloads for a regular n-ary function in c++. But usually there are
some generalizations that are done on the possibilities that
practically cut them to around log(n)^k to n*log(n)^k sets and subsets
(just top of my head estimation).

Hopefully. At least if n is large.
In both cases that should be done using the hierarchy and
templates.
* For conviniency I'll base on the hipothetic language feature. But
what I mean is that it should be possible to create a library that
would support the same general constructs, just instead of syntax
you'll have to define classes and static variable of the library type.

It certainly isn't difficult to come up with a solution for
a given hierarchy. I'm not sure about a generic solution;
I haven't given it a try, but it seems like it should be
possible. The problem is to make the lookup as effective as
possible: if you require n^k functions, each for the most
derived class, you can use a hash table of some sort on the two
(or more) type_info's. If you don't, it's more difficult, since
type_info doesn't give you information about the base classes.
And you need to specify some sort of ordering: either first
match, and insist that the client codinsert in the correct
order (with the most specific functions where they will be found
first), or best match, and you have to define "best", evaluate
all matches, and define behavior in case of ambiguity. It's not
an easy task.
 
I

itaj sherman

It certainly isn't difficult to come up with a solution for
a given hierarchy.  I'm not sure about a generic solution;
I haven't given it a try, but it seems like it should be
possible.  The problem is to make the lookup as effective as
possible: if you require n^k functions, each for the most
derived class, you can use a hash table of some sort on the two
(or more) type_info's.  If you don't, it's more difficult, since
type_info doesn't give you information about the base classes.

I wouldn't mind if it was intrusive. I.e. for every class in the
hierarchy the user would have to state the list of base classes.
And you need to specify some sort of ordering: either first
match, and insist that the client codinsert in the correct
order (with the most specific functions where they will be found
first), or best match, and you have to define "best", evaluate
all matches, and define behavior in case of ambiguity. It's not
an easy task.

I thought though there might have been such a library already
somewhere.

itaj
 
L

ld

Well, that is basically my idea. Although when you work in C, the
language doesn't offer almost any of the features of this object
system (classes, inheritance, function resolution, templates?), so it
seems right to use it for everything. Unlike C, C++ has core features
for everything that this library does, except multimethods.

Well, multimethod is only the beginning of the game. The true feature
of COS is fast message multi-dispatch and generic delegation (message
forwarding) which require to transparently (on-the-fly) to marshall
(caller site) and un-marshall (callee site) the arguments of the
multimethods.

Coming back to C++, the problem with multimethod is that C++ support
multiple inheritance. It's a killer for generic manipulation of
function pointers and makes it's object model particularly complex
(and not easily extensible). The only way I found to partially solve
the problem is to build on-the-fly a (templated) pimpl object specific
to each multimethod with functor capabilities (in the C++ sense). The
idea is to shift problematic generic pointer to functions to
manageable generic pointer to simple objects. The rest relies more on
highly specialized (very fast) hash tables and algebraic properties of
group generator (for keys). On the other hand, generic delegation is
beyond what C++ can do because it already has too many core features
(main advantage of C).
Principally I want to use the core features of C++ to write my code. I
only need a library that supports multimethods, that would extend them
over hirarchies that are already written with core C++ features. So in
that sense, I don't think I can afford transforming all my hirarchies
into any library. That would beat the purpose of finding the crispiest
idiom for multimethods.

I understand your point. My conclusion from trying to port COS to C++
(still a dream) is that C++ needs native support for multimethods (cf.
BS paper for the best proposal I know) because multiple inheritance is
itself a core feature of C++. Otherwise, either code complexity,
compilation time or runtime speed will strongly suffer from
multimethods, if I compare to COS which is extremely simple for its
users and very fast.
 
L

Larry Evans

On 02/22/11 16:08, Larry Evans wrote:
[snip]
Let me see if I can implement this with reifier_visitor.
I'll get back to you.

After some corrections to:

replace_source_with_target_ptr.hpp

got the attached to run and print:

Lion jumps on Girrafe and bites its ass.

However, when the #if on line 188 is switch, I get
very obscure compile-time error messages. So, it
can't handle concrete references. It apparently
can only handle abstract references.

I'll see if I can correct that.
The compiler error messages state that:

hosts_concrete<Lion>

is an incomplete type in reifier_visitor.hpp line 209.
This is where there's a hosts_concrete<HeadAbstract>
and HeadAbstract is Lion which is not abstract.
hosts_concrete is supposed to be specialized on
the abstract class to return the set of concrete
classes for that abstract class. Since Lion already
is an concrete class, there's no need to specialize
hosts_concrete on it; hence, the error.

Will think about solution.
One solution is to define a new metafunction in reifier_visitor.hpp:

template
< typename HostConcretestruct host_abstract
/**@brief
* Metafunction returning Abstract class
* of which HostConcrete is a member.
* IOW, does inverse of hosts_concrete.
*/
{
typedef
HostConcrete
type
//By default, HostConcrete is member
//of a type hierarchy starting at
//HostConcrete.
;
};

and them modify the argument, HeadAbstract, to hosts_concrete to
super of reifier_visitor by host_abstract<HeadAbstract>::type.
That converts any concrete type back to its abstract super.
Of course that's less than ideal because it requires
specializations of host_abstract to map all the derived classes
back to their abstract classes and causes mover virtual
function calls to undo what was just done by host_abstract.

Will upload the modified reifier_visitor.hpp to sandbox.
The modified driver, with all the host_abstract specializations,
is attached.

-Larry
A better solution has just been uploaded. It uses boost::bind to 1st
bind the concrete arguments before then calling
reify_apply<reifier_visitor>(bound_f,...) to reify the remaining
abstract argument and then call the partialy bound function with those
args. The avoids the need for specializing head_abstract on each
concrete class.

The output of the currently uploaded predator_prey.cpp is:

Lion jumps on Girrafe and bites its ass.
Anaconda ignores Girraffe.
Bear mauls Gazelle
Lion jumps on Gazelle and bites its neck.
 
L

Larry Evans

If g++ is available, you can use the -std=gnu++0x option to enable
variadic templates and then use the code demonstrated the
test driver here:
http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp
[snip]
So, after I managed to check out the code, it makes more sense.
But, tell me if the following is correct:
using this library on a hirarchy of classes and a certain multimethod.
- I have to collect the declarations of all different overrides in the
same functor class, like functor3 and functor_any do.
Yes.

- I have to collect all the concrete classes of the hirarchy in one
list, as under hosts_concrete<>

Yes. Otherwise, how could you define the abstract visitor class in an
extensible way?

I suppose the general idea could maybe be to hold some static variable
of dispatch matrices and edit it in constructors of other static
variables.

Not AFAICT. What's needed is a specialization of hosts_concrete for
each abstract class. For example, see the current predator_prey.cpp:

http://svn.boost.org/svn/boost/sand...posite_storage/sandbox/pack/predator_prey.cpp

There's two specializations of hosts_concrete:

template<>
struct hosts_concrete
< Predator_abstract: bmpl::package
< Lion
, Bear
, Anaconda{
};

template<>
struct hosts_concrete
< Prey_abstract: bmpl::package
< Gazelle
, Girrafe{
};

Each argument after the functor argument to
reify_apply<reifier_visitor> must be a HostAbstract& for some
HostAbstract such that hosts_concrete said:
But that is only good for pure runtime dispatch, from what I figured,
your library is about compile type dispatching,

No. In each case, reifier_visitor and reifier_switch, dispatching is
done a runtime:

reifier_visitor:
This uses the classic visitor pattern where the abstract visit
methods occur in the superclasses of reifier_visitor. These
reifier_visitor superclasses are the:

reifier_visit_concrete_pack
< ReifierVisitor
, ConcretePkg
, ConcreteHead
, ConcreteTail...
classes. Each defines the visit method for ConcreteHead. Since
they are inherited one after the other, ultimately there's one
visit method for each class in
hosts_concrete<HostAbstract>::type.

reifier_switch:
For this method, there's no need for something like hosts_concrete
since the set of concrete classes, {C1,C2,...,Cn} are explicitly
listed in the template arguments to:

composite_storage::pack::container
< one_of_maybe
, Index0
, C1,C2,...,Cn

In place of the virtual function calls to reify the abstract class
into its concrete class, the reifier_switch uses a switch
statement on the one_of_maybe which() result.
so maybe it doesn't have enough in common with the solution I'm
looking for. I'm not sure.

I hope the above clarifies the issue.

[snip]
Mainly I deduced my conclusion from the test code, seeing there how
you used reifier_visitor and reifier_apply. I just gazed at the
code of reifier_visitor, I don't know how it works at all, neither
the difference with reifier_switch.

The reify_apply trades calls with the reifier_XXX, where XXX in
{switch,visitor} n times, where n is the number of arguments to be
applied to the reify_apply functor argument. During each such trade,
the reifier reifies the next abstract argument to produce the concrete
counterpart, then calls reify_apply_impl with the modified argument
list. When reify_apply_impl is called with the complete concretized
argument list(see reify_apply.hpp:68), it applies the functor to the
concrete argument list.

Does that make things clearer?

HTH.

-regards,
Larry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top