is there a simple solution to this template problem?

A

aaragon

Hi everyone,

The idea is quite simple: generate a container with random values in
it. For that, I decided to create a class that I called
RandomContainer that inherits from a container (with default value
std::vector<T>). To handle the different ways that the values are
randomized, I created a traits class. The problem that I have, is that
the function randomize() within the traits class takes different
parameters depending on the type (bool takes none, int takes one
parameter and double takes two parameters). Is there a way to make
this function randomize() a generic function? How do I handle the
different parameters? The code is as follows:

// no definition for generic type
template <typename T>
class AlleleTraits {};

// partial specialization for boolean type
template<>
struct AlleleTraits<bool> {

static bool randomize() {
return flip(0.5);
}
};

// partial specialization for integer type
template<>
struct AlleleTraits<int> {

static int randomize(int k) {
return rnd(0, k-1);
}
};

// partial specialization for double type
template<>
struct AlleleTraits<double> {

static double randomize(double low, double high) {
return rnd(low, high);
}
};

// RandomContainer class
template <
typename T,
struct RandomContainer : public Container {

typedef typename Container::iterator iterator;

RandomContainer(size_t size) : Container(size) {}


void initialize(int k) {
for(int i=0; i<this->size(); ++i) {
this->operator[](i) = TTraits::randomize(k);
}
}

void print() {
for(int i=0; i<this->size(); ++i) {
cout<<" "<<this->at(i);
}
}
};


// main.cxx
....
RandomContainer<int> test(10);
test.initialize(6);
test.print();
....

prints:

1 4 3 1 5 1 4 0 3 1

This is fine because the function randomize(int) was defined within
RandomContainer. If I try to do the same with double or bool I have
compiler errors because of course, those functions are not defined
within the traits class.

Thank you,

 
M

mark.teper

I don't think there is any good way in C++ to do exactly what you want
(have the function take a varying number of arguments based on a
template). However you can obtain nearly the same result by requiring
each of the AlleleTraits to have an internal "initializer" object.
Then instead of passing in different numbers of parameters you can
pass in different initializers ( see code below ).

---------------

// no definition for generic type
template <typename T>
class AlleleTraits {};

// partial specialization for boolean type
template<>
struct AlleleTraits<bool> {

struct initializer {};

static bool randomize(const initializer& val) {
return flip(0.5);
}

};


// partial specialization for integer type
template<>
struct AlleleTraits<int> {

typedef int initializer;

static int randomize(const initializer& k) {
return rnd(0, k-1);
}

};

// partial specialization for double type
template<>
struct AlleleTraits<double> {

struct initializer
{
initializer(double l, double h) { low = l; high = h; }
double low, high;
};

static double randomize(const initializer& init) {
return rnd(low, high);
}

};

// RandomContainer class
template <
typename T,
class Container = vector<T>,
class TTraits = AlleleTraits<T> >

struct RandomContainer : public Container {

typedef typename Container::iterator iterator;
typedef typename AlleleTraits<T>::initializer initializer;

RandomContainer(size_t size) : Container(size) {}

void initialize(const initializer& v) {
for(unsigned int i=0; i<this->size(); ++i) {
this->operator[](i) = TTraits::randomize(v);
}
}

void print() {
for(unsigned int i=0; i<this->size(); ++i) {
cout<<" "<<this->at(i);
}
}
};

// main.cxx

.....
RandomContainer<bool> test(6);
test.initialize(AlleleTraits<bool>::initializer());
test.print();

RandomContainer<int> test2(6);
test2.initialize(AlleleTraits<int>::initializer(2));
test2.print();

RandomContainer<double> test3(6);
test3.initialize(AlleleTraits<double>::initializer(.2, .5));
test3.print();
.....

Hopefully that's more or less what your are looking for,

Mark
 
A

aaragon

I don't think there is any good way in C++ to do exactly what you want
(have the function take a varying number of arguments based on a
template). However you can obtain nearly the same result by requiring
each of the AlleleTraits to have an internal "initializer" object.
Then instead of passing in different numbers of parameters you can
pass in different initializers ( see code below ).

---------------

// no definition for generic type
template <typename T>
class AlleleTraits {};

// partial specialization for boolean type
template<>
struct AlleleTraits<bool> {

struct initializer {};

static bool randomize(const initializer& val) {
return flip(0.5);
}

};

// partial specialization for integer type
template<>
struct AlleleTraits<int> {

typedef int initializer;

static int randomize(const initializer& k) {
return rnd(0, k-1);
}

};

// partial specialization for double type
template<>
struct AlleleTraits<double> {

struct initializer
{
initializer(double l, double h) { low = l; high = h; }
double low, high;
};

static double randomize(const initializer& init) {
return rnd(low, high);
}

};

// RandomContainer class
template <
typename T,
class Container = vector<T>,
class TTraits = AlleleTraits<T> >

struct RandomContainer : public Container {

typedef typename Container::iterator iterator;
typedef typename AlleleTraits<T>::initializer initializer;

RandomContainer(size_t size) : Container(size) {}

void initialize(const initializer& v) {
for(unsigned int i=0; i<this->size(); ++i) {
this->operator[](i) = TTraits::randomize(v);
}
}

void print() {
for(unsigned int i=0; i<this->size(); ++i) {
cout<<" "<<this->at(i);
}
}

};

// main.cxx

....
RandomContainer<bool> test(6);
test.initialize(AlleleTraits<bool>::initializer());
test.print();

RandomContainer<int> test2(6);
test2.initialize(AlleleTraits<int>::initializer(2));
test2.print();

RandomContainer<double> test3(6);
test3.initialize(AlleleTraits<double>::initializer(.2, .5));
test3.print();
....

Hopefully that's more or less what your are looking for,

Mark


Mark, thanks for replying, I like your solution. This is what I came
up with before reading yours:

template <typename T>
class AlleleTraits {}
;

template<>
struct AlleleTraits<bool> {

static bool randomize() {
return flip(0.5);
}
};

template<>
struct AlleleTraits<int> {

static int low_;
static int high_;

static int randomize() {
return rnd(low_, high_-1);
}
static int cardinality() {
return high_ - low_;
}
static void setLimits(int low, int high) {
low_ = low;
high_ = high;
}
};
int AlleleTraits<int>::low_;
int AlleleTraits<int>::high_ = 2;

template<>
struct AlleleTraits<double> {

static double low_;
static double high_;

static double randomize() {
return rnd(AlleleTraits<double>::low_,
AlleleTraits<double>::high_);
}
static void setLimits(double low, double high) {
low_ = low;
high_ = high;
}
static double range() {
return high_ - low_;
}

};
double AlleleTraits<double>::low_;
double AlleleTraits<double>::high_ = 1;

template <
typename T,
struct RandomContainer : public Container {

typedef T ValueType;
typedef typename Container::iterator Iterator;

RandomContainer(size_t size) : Container(size) {}


static void setLimits(T low, T high) {
AlleleTraits<T>::setLimits(low,high);
}

void initialize() {
for(int i=0; i<this->size(); ++i) {
this->operator[](i) = TTraits::randomize();
}
}

void print() {
for(int i=0; i<this->size(); ++i) {
cout<<" "<<this->at(i);
}
}
};

// main.cxx
// ...
RandomContainer<bool> test0(10);
test0.initialize();
test0.print();

RandomContainer<int> test(10);
RandomContainer<int>::setLimits(3,9);
test.initialize();
test.print();

RandomContainer<double> test2(10);
RandomContainer<double>::setLimits(0.22,9.333);
test2.initialize();
test2.print();
// ...


0 1 0 0 0 1 1 0 1 0
5 4 5 4 8 7 3 3 7 7
0.368547 2.43343 1.47059 7.54846 1.64782 3.87381 1.40278 1.21157
9.3232 2.20898

However, I will change it so the RandomContainer definition takes
another template parameter and I won't provide a default value for it
(I think the behavior of the container has to be something that the
client has to specify in a single statement and completely). As a
matter of fact, your solution could enter here quite nicely:

template <
typename T,
TTraits::initializer,
struct RandomContainer : public Container {
//...
};

The thing is that this code has to be written by the client, and I
want to make it as easy as possible. So, I don't know yet how to
implement this because I don't want the client to write code like the
following:

typedef RandomContainer<double, AlleleTraits<double>::initializer>
doubleContainer;

I will definitely keep the static variables within the AlleleTraits
classes though. Maybe writing a macro that expands the previous code
would be a solution to make the code simpler to the client? I don't
know. Any other ideas?

 
Z

Zachary Turner

The thing is that this code has to be written by the client, and I
want to make it as easy as possible. So, I don't know yet how to
implement this because I don't want the client to write code like the
following:

typedef RandomContainer<double, AlleleTraits<double>::initializer>
doubleContainer;

Do you mind if they have to write code like this?

typedef RandomContainer<double, AlleleTraits> doubleContainer;

If not, you can declare RandomContainer like this:


template<typename _T, template<typename> class _Traits>
class RandomContainer
{
private:
typedef typename _Traits<_T>::initializer _Initializer;
};
 
R

red floyd

Zachary said:
Do you mind if they have to write code like this?

typedef RandomContainer<double, AlleleTraits> doubleContainer;

If not, you can declare RandomContainer like this:


template<typename _T, template<typename> class _Traits>
class RandomContainer
{
private:
typedef typename _Traits<_T>::initializer _Initializer;
};

Actually, you can't declare it as such. You have generated ill-formed
code, as many of your identifiers (_T, _Initializer, and _Traits) are
reserved to the implementation.
 
A

aaragon

Actually, you can't declare it as such. You have generated ill-formed
code, as many of your identifiers (_T, _Initializer, and _Traits) are
reserved to the implementation.

It is true. Besides, I don't think there is a way to include doubles
in constant expressions, so no matter how I try, I won't be able to
initialize everything with a single type definition. I guess the best
approach so far is the one using static functions to set the limits
of the container after its type definition. Any other ideas?
 
Z

Zachary Turner

It is true. Besides, I don't think there is a way to include doubles
in constant expressions, so no matter how I try, I won't be able to
initialize everything with a single type definition. I guess the best
approach so far is the one using static functions to set the limits
of the container after its type definition. Any other ideas?- Hide quoted text -

Ok, so remove the underscores and change the name to whatever you
want. Can you give an example that illustrates what you won't be able
to accomplish using the above approach? I'm not sure I follow.
 
A

aaragon

Ok, so remove the underscores and change the name to whatever you
want. Can you give an example that illustrates what you won't be able
to accomplish using the above approach? I'm not sure I follow.

Well, I was hoping the user to declare everything in the container
with a single line, something like the following:

typedef RandomContainer<double, 0.2, 9.9> doubleContainer;
typedef RandomContainer<int, 1, 8> intContainer;
typedef RandomContainer<bool> boolContainer;

but I don't think something like this is possible because you cannot
use double values in constant expressions. Also, this type definition
is a part of a bigger type definition so I must be able to do this at
compile time. That's why I think the only way to do it is with static
functions to set the limits of the container:

typedef RandomContainer<double> doubleContainer;
typedef RandomContainer<int> intContainer;
typedef RandomContainer<bool> boolContainer;

doubleContainer::setLimits(0.2,9.9);
intContainer::setLimits(1,8);

If there is a better way to accomplish the same thing, please let me
know.

 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top