Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to adapt static polymorphic classes to dynamic polymorphic behavior?

2 views
Skip to first unread message

marco.g...@gmail.com

unread,
Dec 24, 2009, 5:38:42 PM12/24/09
to
Dear all,

I have problems in mixing static and dynamic polymorphism.
I need dynamic polymorphism in order to create "heterogeneous"
containers (e.g., std::vector<base*>).
Specifically, I have some "untouchable classes" (i.e., coming from
third party libraries) which represent
* random number generators
--- [code] ---
template <typename IntT, IntT a, IntT c, IntT m>
class linear_congruential
{
IntT operator()() {....} // Generate a random number
};
template <typename UIntT, size_t w, size_t n, size_t m, size_t r,
UIntT a, size_t u, size_t s, UIntT b, size_t t, UIntT c, size_t l>
class mersenne_twister
{
IntT operator()() {....} // Generate a random number
};
--- [/code] ---

* probability distributions
--- [code] ---
template <typename RealT>
class exponential {...};
template <typename RealT, typename GeneratorT>
RealT rand(exponential<RealT> const& d, GeneratorT& g) {...}

template <typename RealT>
class normal {...};
template <typename RealT, typename GeneratorT>
RealT rand(normal<RealT> const& d, GeneratorT& g) {...}
--- [/code] ---

Now since I need both to store different probability distributions
inside a vector and to decide what distribution to use at run-time,
I'm trying to create a set of adaptor and base classes in order to get
dynamic polymorphism.
Something like this:
--- [code] ---
namespace my {

template <typename RealT> class base_distribution {...};
template <typename DistributionT> class distribution_adaptor: public
base_distribution<typename DistributionT::real_type> {...};
} // Namespace my

int main() {
//...
std::vector<my::base_distribution<double>*> distrs;
distrs.push_back( new distribution_adaptor<normal>(normal(0,1)) );
distrs.push_back( new distribution_adaptor<exponential>(exponential
(1)) );
}
--- [/code] ---

The problem is how to glue with the "rand" free function during the
iteration of distrs vector?

Since "virtual template methods" are not allowed, a possible solution
might be to bind the random generator to the definition of the
base_distribution class (and of the distribution_adaptor as well):
--- [code] ---
namespace my {
//...

template <typename RealT, typename GeneratorT>
class base_distribution
{
virtual RealT rand(GeneratorT& rng) = 0;
};
template <typename DistributionT, typename GeneratorT>
class distribution_adaptor: public base_distribution<typename
DistributionT::real_type, GeneratorT>
{
typedef DistributionT distr_type;
typedef typename distr_type::real_type real_type;

real_type rand(GeneratorT& rng) { return ::my::rand(rng); }
};
} // Namespace my
int main()
{
//...
typedef linear_congruential<...> generator_type:
std::vector<my::base_distribution<double,generator_type>*> distrs;
distrs.push_back( new distribution_adaptor<normal,generator_type>
(normal(0,1)) );
distrs.push_back( new distribution_adaptor<exponential,generator_type>
(exponential(1)) );
generator_type rng(123456);
for (
std::vector<my::base_distribution<double,generator_type>*>::iterator
it = distrs.begin();
it != distrs.end();
++it
) {
(*it)->rand(rng);
}
--- [/code] ---

However this solution is very bad to me since it bind the random
generator to the design of the probability distribution; furthermore,
it does not allow to choose the random generator at run-time (actually
not needed, but maybe in the future)

So, do you have a more effective way to solve this problem?

Thank you very much in advance!!

NOTE: if you want I post the entire source code. I didn't do it since
this post is already very long.

Best Regards,

-- Marco

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

terminator

unread,
Dec 25, 2009, 6:32:17 PM12/25/09
to
On Dec 25, 1:38 am, "marco.guazz...@gmail.com"
<marco.guazz...@gmail.com> wrote:

> NOTE: if you want I post the entire source code. I didn't do it since
> this post is already very long.
>

I think everybody agrees on this one.
There is too much stuff,making it incomprehencible.
I did not grasp the problem ,but if "rand" is to behave like a
function ,one can use dynamic function object classes to adapt that
too.

reguards,
FM.

marco.g...@gmail.com

unread,
Dec 26, 2009, 1:23:05 PM12/26/09
to
On Dec 26, 12:32 am, terminator <farid.mehr...@gmail.com> wrote:
> On Dec 25, 1:38 am, "marco.guazz...@gmail.com"
>
> <marco.guazz...@gmail.com> wrote:
> > NOTE: if you want I post the entire source code. I didn't do it since
> > this post is already very long.
>
> I think everybody agrees on this one.
> There is too much stuff,making it incomprehencible.

Thanks for the advice. Here below is the sample code. Hope this makes
my problem clearer.
Note: my aim is to wrap third party probability distributions
libraries (represented by namespace "lib", in the code below) into
dynamic polymorphic classes (represented by namespace "my", in the
code below) in order to take advantage of runtime polymorphism and
hence to choose what probability/random generator to use at runtime
and to create vector of different probability distributions (see the
"main" function for an example).

Thank you very much for any hints!!!

--- [code] ---
#include <iostream>
#include <stdint.h>
#include <vector>

namespace lib {

/// "lib" contains untouchable classes

/// RANDOM NUMBER GENERATORS

template <typename ResultT, ResultT a, ResultT c, ResultT m>
struct linear_congruential
{
typedef ResultT result_type;

linear_congruential(result_type seed=1)
: x_(seed)
{
}

result_type operator()()
{
return x_ = (x_*a + c) % m;
}

result_type x_;
};
typedef linear_congruential<uint32_t, 48271, 0, 2147483647> minstd;


/// PROBABILITY DISTRIBUTIONS

template <typename RealT=double>
struct exponential
{
typedef RealT real_type;
exponential(real_type lambda)
: lambda_(lambda)
{
}

real_type lambda_;
};

template <typename RealT=double>
struct normal
{
typedef RealT real_type;
normal(real_type mean, real_type sd)
: mean_(mean),
sd_(sd)
{
}

real_type mean_;
real_type sd_;
};


/// Free polymorphic functions

template <typename RealT, typename GeneratorT>

RealT rand(normal<RealT> const& dist, GeneratorT& rng)
{
std::cout << "In rand(normal,...)" << std::endl;
return RealT(0);
}

template <typename RealT, typename GeneratorT>

RealT rand(exponential<RealT> const& dist, GeneratorT& rng)
{
std::cout << "In rand(exponential,...)" << std::endl;
return RealT(0);
}

} // Namespace lib


namespace my {

/// "my" contains custom wrapper and base classes for dynamic
polymorphism

/// RANDOM NUMBER GENERATORS

template <typename ResultT>
struct base_generator
{
typedef ResultT result_type;

virtual result_type operator()() = 0;
};

template <typename GeneratorT>
struct generator_adaptor: public base_generator<typename
GeneratorT::result_type>
{
typedef GeneratorT generator_type;
typedef typename generator_type::result_type result_type;

generator_adaptor(generator_type const& rng)
: rng_(rng)
{
}

result_type operator()()
{
return rng_();
}

generator_type rng_;
};

template <typename GeneratorT>
base_generator<typename GeneratorT::result_type>* make_generator
(GeneratorT const& rng)
{
return new generator_adaptor<GeneratorT>(rng);
}


/// PROBABILITY DISTRIBUTIONS

// Need to bind GeneratorT at declaration-time in order to create
virtual methods
// This is a *bad* choice since GeneratorT does not belong to the
class state (i.e., there is no
// data member with type GeneratorT)!!


template <typename RealT, typename GeneratorT>

struct base_distribution
{
typedef RealT real_type;

virtual real_type rand(GeneratorT& rng) = 0;
};

template <typename RealT, typename GeneratorT>
RealT rand(base_distribution<RealT, GeneratorT> const& dist,
GeneratorT& rng)
{
std::cout << "In rand(base_distribution,...)" << std::endl;
return dist.rand(rng);
}

// Need to bind GeneratorT at declaration-time in order to create
virtual methods
// This is a *bad* choice since GeneratorT does not belong to the
class state (i.e., there is no
// data member with type GeneratorT)!!


template <typename DistributionT, typename GeneratorT>

struct distribution_adaptor: public base_distribution<typename
DistributionT::real_type,GeneratorT>
{
typedef DistributionT distribution_type;
typedef typename distribution_type::real_type real_type;

distribution_adaptor(distribution_type const& dist)
: dist_(dist)
{
}

distribution_type& distribution()
{
return dist_;
}

distribution_type distribution() const
{
return dist_;
}

real_type rand(GeneratorT& rng)
{
return ::lib::rand<real_type,GeneratorT>(dist_, rng);
}

distribution_type dist_;
};


/// Free polymorphic functions and generators

template <typename DistributionT, typename GeneratorT>

typename DistributionT::real_type rand
(distribution_adaptor<DistributionT,GeneratorT> const& dist,
GeneratorT& rng)
{
std::cout << "In rand(distribution_adaptor,...)" << std::endl;
return rand(dist.distribution(), rng);
}

template <typename DistributionT,typename GeneratorT>
base_distribution<typename DistributionT::real_type,GeneratorT>*
make_distribution(DistributionT const& d)
{
return new distribution_adaptor<DistributionT,GeneratorT>(d);
}

} // Namespace my


int main()
{
typedef double real_type;
typedef lib::minstd generator_type;
typedef my::base_distribution<real_type, generator_type>*
ptr_base_distr_type;

::std::vector<ptr_base_distr_type> distrs;

distrs.push_back
(my::make_distribution<lib::exponential<real_type>, generator_type>
(lib::exponential<real_type>(2.5)));
distrs.push_back(my::make_distribution<lib::normal<real_type>,
generator_type>(lib::normal<real_type>(0, 2)));

generator_type rng(1234);

for (
std::vector<ptr_base_distr_type>::const_iterator it =


distrs.begin();
it != distrs.end();
++it
) {
(*it)->rand(rng);
}

// clean-up
while (!distrs.empty())
{
delete distrs.back();
distrs.pop_back();
}
}
--- [/code] ---

Best Regards,

-- Marco

Alp Mestan

unread,
Dec 30, 2009, 12:43:19 AM12/30/09
to
On Dec 24, 11:38 pm, "marco.guazz...@gmail.com"

<marco.guazz...@gmail.com> wrote:
> Dear all,
>
> I have problems in mixing static and dynamic polymorphism.
> I need dynamic polymorphism in order to create "heterogeneous"
> containers (e.g., std::vector<base*>).

You may want to search for the "type erasure" key words.

marco.g...@gmail.com

unread,
Dec 31, 2009, 3:02:26 PM12/31/09
to
On Dec 30, 6:43 am, Alp Mestan <alpmes...@gmail.com> wrote:
> On Dec 24, 11:38 pm, "marco.guazz...@gmail.com"
>
> <marco.guazz...@gmail.com> wrote:
> > Dear all,
>
> > I have problems in mixing static and dynamic polymorphism.
> > I need dynamic polymorphism in order to create "heterogeneous"
> > containers (e.g., std::vector<base*>).
>
> You may want to search for the "type erasure" key words.
>

Thank you!!

I've found these 2 very interesting articles:
1. Peter Pirkelbauer, Sean Parent, Mat Marcus, and Bjarne Stroustrup.
"Runtime Concepts for the C++ Standard Template Library".
In Proc. of the 2008 ACM symposium on Applied computing (SAC'08),
March 2008.
[Found at: http://www.research.att.com/~bs/oops08.pdf]
2. Thomas Becker.
"Type erasure in C++: The glue between object oriented and generic
programming".
In K. Davis and J. Striegnitz, editors, Multiparadigm Programming
2007: Proc. of the MPOOL Workshop at ECOOP'07, July 2007.
[Found at: http://homepages.fh-regensburg.de/~mpool/mpool07/proceedings/4.pdf]

According to what I've understood, I've elaborated the following
solution.
Firstly, I've created the "any_generator<ResultT>" class which uses
type-erasure to abstract the random number generator concept.
Then, I've created the "any_distribution<InputT,RealT>" class which
uses type-erasure to abstract the random distribution concept.
In order to attach a random number generator to a generic
"any_distribution" I evaluated two alternatives:
a) To use directly the "any_generator" class.
b) To create another wrapper representing a generic uniform random
number generator in [0,1]
I've decided for alternative (b) since alternative (a) would imply to
add another template parameter to the definition of the
"any_distribution" class in order to pass it to the "any_generator"
class (note that the type returned by the generator can be in
principle different from the ones defined for the distribution; for
instance, the linear_congruential generator is defined on integer,
while the normal distribution is defined on real numbers).
That is, by using (a) the resulting any_distribution would be:
--- [code] ---
template <typename InputT, typename RealT, typename GeneratorResultT>
struct any_distribution
{
// This is part of the distribution concept and should be declarable
as pure virtual
InputT rand(any_generator<GeneratorResultT>& rng) const { ... }
};
--- [/code] ---

Here below is the entire code.

Please, since I'm very new to generic programming, it would be great
if you help me understand where the code could be improved (from the
point of view of the design) or if a different (better) solution can
be used.

--- [code] ---
#include <boost/smart_ptr.hpp>
#include <cmath>
#include <iostream>
#include <limits>
#include <stdint.h>
#include <vector>

namespace lib {

/// "lib" contains untouchable classes

/// RANDOM NUMBER GENERATORS

template <typename ResultT, ResultT a, ResultT c, ResultT m>
struct linear_congruential
{
typedef ResultT result_type;

linear_congruential(result_type seed=1)
: x_(seed)
{
}

result_type operator()()
{
return x_ = (x_*a + c) % m;
}

result_type min() const
{
return c == 0 ? 1 : 0;
}

result_type max() const
{
return m-1;
}

result_type x_;
};
typedef linear_congruential<uint32_t, 48271, 0, 2147483647> minstd;

/// PROBABILITY DISTRIBUTIONS

template <typename RealT=double>
struct exponential
{

typedef RealT input_type;
typedef RealT real_type;

exponential(real_type lambda)
: lambda_(lambda)
{
}

template <typename GeneratorT>
real_type rand(GeneratorT& rng) const
{
return -real_type(1) / lambda_ * ::std::log(real_type(1)-rng
());
}

template <typename CharT, typename Traits>
friend ::std::basic_ostream<CharT,Traits>& operator<<
(::std::basic_ostream<CharT,Traits>& os, exponential<real_type> const&
dist)
{
return os << "Exponential(" << dist.lambda_ << ")";
}

real_type lambda_;
};

template <typename RealT=double>
struct normal
{

typedef RealT input_type;
typedef RealT real_type;

static const real_type pi = real_type(3.14159265358979323846);

normal(real_type mean, real_type sd)
: mean_(mean),
sd_(sd)
{
}

template <typename GeneratorT>
real_type rand(GeneratorT& rng) const
{
if (!valid_)
{
r1_ = rng();
r2_ = rng();
rho_ = ::std::sqrt(-real_type(2) * ::std::log(real_type(1)-
r2_));
valid_ = true;
}
else
{
valid_ = false;
}
return rho_
* (valid_
? ::std::cos(real_type(2)*pi*r1_)
: ::std::sin(real_type(2)*pi*r1_))
* sd_
+ mean_;
}

template <typename CharT, typename Traits>
friend ::std::basic_ostream<CharT,Traits>& operator<<
(::std::basic_ostream<CharT,Traits>& os, normal<real_type> const&
dist)
{
return os << "Normal(" << dist.mean_ << ", " << dist.sd_ <<
")";
}

real_type mean_;
real_type sd_;
mutable real_type r1_;
mutable real_type r2_;
mutable real_type rho_;
mutable bool valid_;
};


/// Free polymorphic functions

template <typename DistributionT, typename GeneratorT>

typename DistributionT::input_type rand(DistributionT const& dist,
GeneratorT& rng)
{
return dist.rand(rng);
}

} // Namespace lib


namespace my {

/// "my" contains custom wrapper and base classes for dynamic
polymorphism

/// RANDOM NUMBER GENERATORS

namespace detail {

template <typename ResultT>
struct concept_generator
{
typedef ResultT result_type;

virtual result_type operator()() = 0;
};

template <typename GeneratorT>
struct model_generator: concept_generator<typename
GeneratorT::result_type>
{
typedef typename GeneratorT::result_type result_type;

model_generator(GeneratorT& rng)
: rng_(rng)
{
}

result_type operator()()
{
return rng_();
}

GeneratorT& rng_;
};

} // Namespace detail


template <typename GeneratorT, typename RealT>
struct uniform_01_wrapper_generator
{
typedef typename GeneratorT::result_type input_type;
typedef RealT result_type;

uniform_01_wrapper_generator(input_type seed)
: rng_(seed)
{
}

uniform_01_wrapper_generator(GeneratorT rng)
: rng_(rng)
{
}

result_type operator()()
{
for (;;)
{
result_type factor = result_type(1) / (result_type
((rng_.max)()-(rng_.min)()) + result_type
(::std::numeric_limits<input_type>::is_integer ? 1 : 0));
result_type result = result_type(rng_() - (rng_.min)()) *
factor;
if (result < result_type(1))
{
return result;
}
}
}

result_type min() const
{
return 0;
}

result_type max() const
{
return 1;
}

GeneratorT rng_;
};

template <typename ResultT>
struct any_generator
{
typedef ResultT result_type;

template <typename GeneratorT>
any_generator(GeneratorT& rng)
: ptr_rng_(new detail::model_generator<GeneratorT>(rng))
{
}

result_type operator()()
{
return (*ptr_rng_)();
}

::boost::shared_ptr< detail::concept_generator<result_type> >
ptr_rng_;
};


/// PROBABILITY DISTRIBUTIONS

namespace detail {

template <typename InputT, typename RealT>
struct concept_distribution
{
typedef InputT input_type;
typedef RealT real_type;
virtual input_type rand(any_generator<real_type>& rvg) const = 0;
virtual ::std::ostream& print(::std::ostream& os) const = 0;
};

template <typename DistributionT>
struct model_distribution: concept_distribution<typename
DistributionT::input_type, typename DistributionT::real_type>
{
typedef typename DistributionT::input_type input_type;
typedef typename DistributionT::real_type real_type;

model_distribution(DistributionT const& dist)
: dist_(dist)
{
}

input_type rand(any_generator<real_type>& rng) const
{
return ::lib::rand(dist_, rng);
}

::std::ostream& print(::std::ostream& os) const
{
os << dist_;
return os;
}

const DistributionT& dist_;
};

}

template <typename InputT, typename RealT>
struct any_distribution
{
typedef InputT input_type;
typedef RealT real_type;

template <typename DistributionT>
any_distribution(DistributionT const& dist)
: ptr_dist_(new detail::model_distribution<DistributionT>(dist))
{
}

input_type rand(any_generator<real_type>& rng) const
{
return ptr_dist_->rand(rng);
}

::std::ostream& print(::std::ostream& os) const
{
os << ptr_dist_->print(os);
//exit(1);
return os;
}

template <typename CharT, typename Traits>
friend ::std::basic_ostream<CharT,Traits>& operator<<
(::std::basic_ostream<CharT,Traits>& os,
any_distribution<input_type,real_type> const& dist)
{
dist.ptr_dist_->print(os);
return os;
}

::boost::shared_ptr<
detail::concept_distribution<input_type,real_type> > ptr_dist_;
};

} // Namespace my


int main()
{


typedef double real_type;
typedef lib::minstd generator_type;

typedef my::uniform_01_wrapper_generator<generator_type,real_type>
u01_generator_type;
typedef my::any_generator<real_type> any_generator_type;
typedef my::any_distribution<real_type, real_type>
any_distribution_type;

::std::vector<any_distribution_type> distrs;

distrs.push_back(any_distribution_type(lib::exponential<real_type>
(2.5)));
distrs.push_back(any_distribution_type(lib::normal<real_type>(0,
2)));

u01_generator_type rng(1234);
any_generator_type any_rng(rng);

for (
std::vector<any_distribution_type>::const_iterator it =


distrs.begin();
it != distrs.end();
++it
) {

std::cout << "Rand from " << (*it) << ": " << (it->rand
(any_rng)) << std::endl;
}
}
--- [/code] ---

Thank you very much in advance!!!

Best,

-- Marco

0 new messages