I'm working on a C++ class that needs to perform type-specific operations on different data structures. For specific reasons, making class template parameterized is not a solution for me.
but I'm struggling to find a clean solution that doesn't involve significant code duplication. Here's a simplified version of my current code. Note that in do_something
I will write and delete the cache, also get reference of some value of the cache and do computation using it.
Do you have any suggestion to refactor the class?
#include <vector>
// Note that I dont want to use a template class
struct DataBase {
std::vector<double> cached; // std::vector is just an example.
std::vector<double> cache2d;
std::vector<float> cachef;
std::vector<float> cache2f;
// many more data structures with different types (float or double)
template <typename T>
void do_something() {
if (std::is_same<T, double>::value) {
// long lines of code using cached or cache2d
// in my actual code, I have a lot of these like cache3d, cache4d, etc.
// and I use all of them in the function.
} else if (std::is_same<T, float>::value) {
// all the same long lines of code as above
// except using float version of caches rather than double
}
}
};
int main() {
DataBase db;
db.do_something<double>();
db.do_something<float>();
return 0;
}
I would suggest you decouple the real data source with the operations. I would do something like:
struct DataBase {
std::vector<double> cached;
std::vector<double> cache2d;
std::vector<float> cachef;
std::vector<float> cache2f;
};
template <typename T>
struct DbAccessor {
std::vector<T>& cache;
std::vector<T>& cache2;
DbAccessor(std::vector<T>& c, std::vector<T>& c2) : cache(c), cache2(c2) {}
void do_something() {
// db operations to
}
};
int main() {
DataBase db;
// here is the new complexity, but hopefully less complex than your do_something logic
DbAccessor<double>(db.cached, db.cache2d).do_something();
DbAccessor<float>(db.cachef, db.cache2f).do_something();
return 0;
}
I think for a simple application above is a reasonable trade off. However, based on your usage pattern, I may argue that cached
and cachef
can be abstracted to begin with. Check out the following:
template <typename T>
struct TypedDatabase {
std::vector<T> c;
std::vector<T> c2;
};
struct Database {
TypedDatabase<float> cachef;
TypedDatabase<double> cached;
template <typename T>
TypedDatabase<T>& get_cache();
template <>
TypedDatabase<float>& get_cache() { return cachef; }
template <>
TypedDatabase<double>& get_cache() { return cached; }
};
// optionally write this as DbAccessor method
template <typename T>
void do_something(TypedDatabase<T>& cache) {
// operate on cache.c and cache.c2
}