If one creates a class template based on one or more type parameters, how would one query these types at runtime?
For example:
template <typename T>
class Foo {
public:
typedef T TypeT;
Foo() {}
// assume i is in range
void set(size_t i, T value) { store[i] = value; }
T get(size_t i) { return store[i]; }
void fast_copy(T* dest) { memcpy(dest, store, 100 * size_of(T)); }
// ugly public data member
T store[100];
};
void main() {
Foo<double> fd;
Foo<int> fi;
// this is not C++!
if (fd::TypeT == fi::TypeT) {
// do a fast copy since the types match
fd.fast_copy(fi.store);
} else {
// do a slower item by copy since the types don't match
// and let the runtime perform the type conversion:
for (size_t i = 0; i < 100; ++i) {
fi.set(i, static_cast<fd::TypeT>(fd.get(i)));
}
}
}
The reason I want to do this is because I have runtime objects that contain either arrays of floats, doubles or ints, and I want to copy them to an array of a different type, but I don't know what those types will be as they are determined at runtime. In the above example, the types used to instantiate fi and fd are known, but in the full example fi and fd would be references to a polymorphic class that could be containers of an array of any basic numerical type.
This mechanism is intended to be a runtime configurable bridge between a set of functions that return arrays of floats, doubles or ints, and another set that requires arrays of floats, doubles, or ints, and the types may or may not be the same on both sides of the bridge. The bridge is non-trivial and does a few other things, but needs to take an array (or a container of an array) of one type in, and produce array an (or a container of an array) of another type at the other end, in cases where the types are the same and also where they are not.
Note: this is related to this previous question of mine and will replace it if this one makes more sense.
EDIT: here's a bit more info on what I'm trying to do.
I have a system that on one side consists of a set of functions that return numerical results. Some return a single float or double, some a single int, some modify (hence 'return') an array of floats, or doubles, or ints. There's a whole bunch of them. I want to associate each one (well, actually, just the returned data, the functions don't matter) with something I call a "Source" object - this might be Source<T>
, where T is the numerical type. Although I might need SourceScalar<T>
and SourceVector<T>
to handle single values or arrays. Anyway, this object is the entry point to the "Bridge". Each Source<T>
object is stored in a heterogenous collection of Source objects, and referenced by a unique "Source Key". For example, one such Source-associated function might return an array of 32 floats representing the latest output from a wavetable oscillator. Another might return an array of 32 doubles representing a peak detector output over 32 samples.
On the other side of the system I have yet another set of functions. These all take numerical values as parameters. Some require a single float or double, some a single int, some require arrays of the same. There's a whole bunch of these too. I have something I call a "Dest" object - Dest<T>
, or maybe DestScalar<T>
, DestVector<T>
as above. This object stores a std::function wrapper that is tied to one of these functions, so that it can invoke the function when necessary. Each Dest object is stored in a hetereogenous collection of Dest objects, and referenced by a unique "Dest Key". For example, one such function might be a digital filter that expects to receive arrays of size 32, but of doubles not floats.
Now, at runtime, a higher-level system (actually controlled by a user) is required to arbitrarily associate any Source with any Dest. The user provides the two keys - source and dest - and the system 'connects' the data on one side with the function on the other. So in the example above, it might try to connect the 32-float array from the wavetable oscillator to the 32-double array parameter of the digital filter. At any time this association might be removed, too. If the 32-double array from the peak detector is connected to the 32-double array parameter of the digital filter, then it would want the transfer/copy to be as fast as possible because the arrays are of the same type.
EDIT 2: here is some code that compiles and is the simplest case I can construct that creates two collections of Source and Dest objects, and dynamically attempts to 'connect' them. The first two calls to source_collection[1]->set(...)
work correctly, but the second two do not, and end up calling the BaseDest base class member function set_item
rather than the one in Dest<T>
. This also uses the observation that typeid() returns a static pointer if the type queried is the same, and compares the values of these pointers to determine that the type matches. This is probably unsafe, might be best to just use enums instead.
This all feels very horrible to me - there must be a better way?
#include <vector>
#include <typeinfo>
#include <cassert>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
class Source; // fwd
class BaseDest; // fwd
typedef boost::shared_ptr<Source> SourcePtr;
typedef boost::shared_ptr<BaseDest> BaseDestPtr;
// target function that takes an array of doubles
void target_func_vd(double *array, size_t len) {
for (size_t i = 0; i < len; ++i) {
assert(array[i] == i);
}
}
// target function that takes an array of floats
void target_func_vf(float *array, size_t len) {
for (size_t i = 0; i < len; ++i) {
assert(array[i] == i);
}
}
// base class for Dest
class BaseDest {
public:
BaseDest() {}
virtual ~BaseDest() {}
virtual void set(float *array, size_t len) { /* not implemented, but can't be pure */ };
virtual void set(double *array, size_t len) { /* not implemented, but can't be pure */ };
virtual void set_item(size_t index, double item) { /* not implemented, but can't be pure */ };
virtual void set_item(size_t index, float item) { /* not implemented, but can't be pure */ };
virtual void finished(size_t len) = 0;
virtual const std::type_info* get_type_info() const = 0;
private:
};
template <typename T>
class DestVector : public BaseDest {
public:
typedef boost::function<void (T *, size_t)> Callable;
explicit DestVector(Callable callable) : callable_(callable) { }
virtual void set(T *array, size_t len) { callable_(array, len); }
virtual void set_item(size_t index, T item) { buffer_[index] = item; }
virtual void finished(size_t len) { callable_(buffer_, len); }
virtual const std::type_info* get_type_info() const { return &typeid(T); };
private:
Callable callable_;
T buffer_[256];
};
// 'set' is overloaded by array type
class Source {
public:
Source() {}
void connect(const BaseDestPtr& dest) { dest_ = dest; }
void set(float *array, size_t len) {
if (dest_->get_type_info() == &typeid(double)) {
// convert to double
for (size_t i = 0; i < len; ++i) {
dest_->set_item(i, array[i]); // calls the base member function
}
dest_->finished(len);
} else if (dest_->get_type_info() == &typeid(float)) {
dest_->set(array, len);
}
}
void set(double *array, size_t len) {
if (dest_->get_type_info() == &typeid(float)) {
// convert to float
for (size_t i = 0; i < len; ++i) {
dest_->set_item(i, array[i]); // calls the base member function
}
dest_->finished(len);
} else if (dest_->get_type_info() == &typeid(double)) {
dest_->set(array, len);
}
}
private:
BaseDestPtr dest_;
};
void main() {
// test arrays
float float_array[256];
for (size_t i = 0; i < 256; ++i) {
float_array[i] = static_cast<float>(i);
}
double double_array[256];
for (size_t i = 0; i < 256; ++i) {
double_array[i] = static_cast<double>(i);
}
// collection of Sources
std::vector<SourcePtr> source_collection;
SourcePtr s0(new Source());
source_collection.push_back(s0);
SourcePtr s1(new Source());
source_collection.push_back(s1);
// collection of Dests
std::vector<BaseDestPtr> dest_collection;
BaseDestPtr t0(new DestVector<float>(&target_func_vf));
dest_collection.push_back(t0);
BaseDestPtr t1(new DestVector<double>(&target_func_vd));
dest_collection.push_back(t1);
// create and invoke connections
source_collection[0]->connect(dest_collection[0]);
source_collection[0]->set(float_array, 256); // this should end up passing float_array to target_func_vf, and it does
source_collection[0]->connect(dest_collection[1]);
source_collection[0]->set(double_array, 256); // this should end up passing double_array to target_func_vd, and it does
source_collection[1]->connect(dest_collection[0]);
source_collection[1]->set(double_array, 256); // this should end up passing double_array to target_func_vf, but it doesn't
source_collection[1]->connect(dest_collection[1]);
source_collection[1]->set(float_array, 256); // this should end up passing float_array to target_func_vd, but it doesn't
}