I made an N-dimensional structure with vectors and templates:
//----------------N-dimensional vector--------------------------------
template<int dim,typename T> class n_dim_vector {
public:
typedef std::vector<typename n_dim_vector<dim - 1, T>::vector> vector;
};
template<typename T> class n_dim_vector <0, T> {
public:
typedef T vector;
};
It can be instatiaated with different dimnsion-counts and is prt of a class that represent a search space.
template<int dim, typename T> class n_dim_ssc {
private:
typename n_dim_vector<dim, T>::vector searchspace;
};
My problem: I cannot get operator[] right to access searchspace properly, specifically the return type.
I tried:
template<typename V> std::vector<V>& operator[](unsigned i) {
return searchspace[i];
}
T& operator[](unsigned i) {
return searchspace[i];
}
at first, thinking the compiler would derive typename V as whatever type searchspace contained at all but the last level. Thats what T& operator[](unsigned i) was for.
But alas, doen't work this way. And I cannot work out how it would
EDIT Don't fear, I do not access empty memory, the structure is initialized and filled, I just didn't include the code for clarity's sake.
Also, I don't intend to access it with a single integer, I wanted to use searchspace[i][j]..[k]
The way to let compiler deduces the return type is auto:
In C++14:
auto operator[](unsigned i) { return searchspace[i]; }
In C++11:
auto operator[](unsigned i) -> decltype(searchspace[i]) { return searchspace[i]; }
I'm answering to your comment
Feel free to recommend something better, I'd appreciate it.
The following code shows one way to handle the multidimensional vector at once, i.e. non-recursively. It could be improved in several ways which I didn't consider for now (for instance, I wouldn't want to use and pass that many arrays but rather use variadic parameter lists. This however requires much more and more diffcult code, so I'll let it be.)
#include <numeric>
template<size_t Dim, typename T>
struct MultiDimVector
{
std::array<size_t, Dim> Ndim;
std::array<size_t, Dim> stride;
std::vector<T> container;
MultiDimVector(std::array<size_t, Dim> const& _Ndim) : Ndim(_Ndim), container(size())
{
stride[0] = 1;
for (size_t i = 1; i<Dim; ++i)
{
stride[i] = stride[i - 1] * Ndim[i - 1];
}
}
size_t size() const
{
return std::accumulate(Ndim.begin(), Ndim.end(), 1, std::multiplies<size_t>());
}
size_t get_index(std::array<size_t, Dim> const& indices) const
{
//here one could also use some STL algorithm ...
size_t ret = 0;
for (size_t i = 0; i<Dim; ++i)
{
ret += stride[i] * indices[i];
}
return ret;
}
T const& operator()(std::array<size_t, Dim> const& indices) const
{
return container[get_index(indices)];
}
};
You can use it like
MultiDimVector<3, double> v({ 3, 2, 5 }); //initialize vector of dimension 3x2x5
auto a = v({0,1,0}); //get element 0,1,0
But as I wrote, the curly brackets suck, so I'd rewrite the whole thing using variadic templates.
The problem with your approach is that you're not initializing any memory inside the vector and just trying to return non-existent memory spots. Something on the line of the following (WARNING: uncleaned and unrefactored code ahead):
#include <iostream>
#include <vector>
template<int dim,typename T> class n_dim_vector {
public:
typedef std::vector<typename n_dim_vector<dim - 1, T>::vector> vector;
};
template<typename T> class n_dim_vector <0, T> {
public:
typedef T vector;
};
template<int dim, typename T> class n_dim_ssc {
public:
typename n_dim_vector<dim, T>::vector searchspace;
n_dim_ssc() {}
n_dim_ssc(typename n_dim_vector<dim, T>::vector space) : searchspace(space) {}
n_dim_ssc<dim-1, T> operator[](std::size_t i) {
if(searchspace.size() < ++i)
searchspace.resize(i);
return n_dim_ssc<dim-1, T>(searchspace[--i]);
}
typename n_dim_vector<dim, T>::vector get() {
return searchspace;
}
};
template<typename T> class n_dim_ssc<0,T> {
public:
typename n_dim_vector<0, T>::vector searchspace;
n_dim_ssc() {}
n_dim_ssc(typename n_dim_vector<0, T>::vector space) : searchspace(space) {}
typename n_dim_vector<0, T>::vector get() {
return searchspace;
}
};
int main(int argc, char** argv) {
n_dim_ssc<0, int> ea;
int a = ea.get();
n_dim_ssc<1, int> ea2;
auto dd2 = ea2[0].get();
n_dim_ssc<2, int> ea3;
auto dd3 = ea3[0][0].get();
}
Try it out
will work with an accessor method (you can modify this as you want).
Anyway I strongly have to agree with Kerrek: a contiguous memory space accessed in a multi-dimensional array fashion will both prove to be faster and definitely more maintainable/easier to use and read.
Related
I want to create a function template that creates a list of all legal/valid instances of some class. The class itself is somehow informed about the values that each of its members can take. Function template:
template <typename T>
std::list<T> PossibleInstantiations();
Now, if SomeClass somehow contains information about legal instantiations of all its members (in below example, legal instantiations of i are 1,4,5 and legal instantiations of j are 1.0, 4.5), then
PossibleInstantiations<SomeClass>();
should yield a list containing elements {SomeClass(1,1.0), SomeClass(1,4.5), SomeClass(4,1.0), SomeClass(4,4.5), SomeClass(5,1.0), SomeClass(5,4.5)}.
Of course, adding extra elements (+ associated valid values) should automatically be handled by PossibleInstantiations.
Someclass would be implemented something like below. In what way should the plumbing be added to client classes (e.g. MyClass), and how should PossibleInstantiations be implemented?
class SomeClass
{
public:
int i;
static std::list<int> ValidValuesFori();
double j;
static std::list<double> ValidValuesForj();
SomeClass(int i, double j);
//int k;
//static std::list<int> ValidValuesFork(); //could be implemented at some later stage.
//Which would mean the constructor becomes:
//SomeClass(int i, int j, int k)
//...
//Extra wiring for pointing out that i and ValidValuesFori belong to each other,
//and perhaps for pointing out that i is the first element in the constructor, or so?
//..
};
static std::list<int> SomeClass::ValidValuesFori()
{
return std::list<int>{1, 4, 5};
//Other options:
//std::list<int> ValidValues;
//for (int i = 0; i < 1000; i++)
//{
// if (i % 3 == 0)
// ValidValues.push_back(i);
//}
//return ValidValues;
}
static std::list<double> SomeClass::ValidValuesForj()
{
return std::list<double>{1.0, 4.5};
}
SomeClass::SomeClass(int i, double j)//or other constructor
:i{ i }, j{ j } {}
Why make it hard if you can make it easy? You already say that SomeClass should know which values are allowed for its members. You could make this explicit by moving GetPossibleImplementations() to the class:
class SomeClass
{
public:
static std::vector<SomeClass> GetPossibleImplementations()
{
std::vector<SomeClass> values;
for (int i : ValidValuesFori())
for (double j : ValidValuesForj())
values.push_back(SomeClass(i, j));
return values;
}
// other methods
// [...]
}
Then you can still add the template function, if you need it:
template <typename T>
std::vector<T> GetPossibleImplementations()
{
return T::GetPossibleImplementations();
}
Moving the logic into the class has the following advantages:
Only the class knows which constructor args are valid, so it makes sense to have the logic there.
The cartesian product of valid argument values may not be sufficient for all cases. What if some value of i conflicts with some value of j?
You can still move some logic into helper functions, for example, the cartesian product.
There are situations where you cannot change the implementation of SomeClass and you need an external solution. Even in that case, I think you should keep the logic class-specific: Declare the generic function GetPossibleImplementations<T>() as above, but implement it only for the specific classes:
template <typename T>
std::vector<T> GetPossibleImplementations();
template <>
std::vector<SomeClass> GetPossibleImplementations<SomeClass>()
{
std::vector<SomeClass> values;
for (int i : SomeClass::ValidValuesFori())
for (double j : SomeClass::ValidValuesForj())
values.push_back(SomeClass(i, j));
return values;
}
The main differences between the two versions is that the first version yields a compile error if the template argument T does not support T::GetPossibleImplementations() and the second version yields a link error if GetPossibleImplementations<T> is not implemented.
With Cartesian product, we might do:
// cartesian_product_imp(f, v...) means
// "do `f` for each element of cartesian product of v..."
template<typename F>
void cartesian_product_imp(F f) {
f();
}
template<typename F, typename H, typename... Ts>
void cartesian_product_imp(F f, std::vector<H> const& h,
std::vector<Ts> const&... vs) {
for (H const& he: h) {
cartesian_product_imp([&](Ts const&... ts){
f(he, ts...);
}, vs...);
}
}
template <typename... Ts>
std::vector<std::tuple<Ts...>> cartesian_product(std::vector<Ts> const&... vs) {
std::vector<std::tuple<Ts...>> res;
cartesian_product_imp([&](Ts const&... ts){
res.emplace_back(ts...);
}, vs...);
return res;
}
template <typename T>
std::vector<T> PossibleInstantiations()
{
auto validValuesByArgs = T::ValidValuesForArgs();
auto validArgs =
std::apply([](const auto&... args){ return cartesian_product(args...); },
validValuesByArgs);
std::vector<T> res;
std::transform(validArgs.begin(), validArgs.end(),
std::back_inserter(res),
[](const auto& t){
return std::apply([](const auto&... args) { return T(args...); },
t);
});
return res;
}
With
class SomeClass
{
public:
int i;
double j;
const std::string s;
static std::tuple<std::vector<int>, std::vector<double>, std::vector<std::string>>
ValidValuesForArgs() { return {{1, 4, 5}, {1.1, 4.5}, {"foo", "bar"}}; }
SomeClass(int i, double j, const std::string& s) : i(i), j(j), s(s) {}
};
Demo
In order to read and store some results from a MATLAB program, I need to use up to 6 dimensional matrices. Instead of doing something like:
typedef std::vector<double> Row;
typedef std::vector<Row> Matrix2;
typedef std::vector<Matrix2> Matrix3;
typedef std::vector<Matrix3> Matrix4;
typedef std::vector<Matrix4> Matrix5;
typedef std::vector<Matrix5> Matrix6;
I decided to go with templates, and here's what I have so far:
template <class T, int N>
class Matrix {
public:
typedef typename Matrix<T, N - 1>::type MatrixOneDimLower;
typedef std::vector<MatrixOneDimLower> type;
type _data;
template <unsigned int dn, typename ...NT>
Matrix(unsigned int dn, NT ...drest) : _data(dn, MatrixOneDimLower(drest)) {}
MatrixOneDimLower& operator[](unsigned int index)
{
return _data[index];
}
};
template <class T>
class Matrix<T, 1> {
public:
typedef std::vector<T> type;
type _data;
Matrix(unsigned int d0) : _data(d0, T(0.0)) {}
T& operator[](unsigned int index)
{
return _data[index];
}
};
Unfortunately, I'm not very adept in variadic templates and recursive templates, and this doesn't work. For example, if I try to use this as:
Matrix<double, 4> temp(n, dim[2], dim[1], dim[0]);
I get this compile time error (Visual Studio 2017):
error C2661: 'Matrix<double,4>::Matrix': no overloaded function takes 4 arguments
I would really appreciate if you can let me know what I'm doing wrong.
template<class T, std::size_t I>
struct MatrixView {
MatrixView<T, I-1> operator[](std::size_t i) {
return {ptr + i* *strides, strides+1};
}
MatrixView( T* p, std::size_t const* stride ):ptr(p), strides(stride) {}
private:
T* ptr = 0;
std::size_t const* strides = 0;
};
template<class T>
struct MatrixView<T, 1> {
T& operator[](std::size_t i) {
return ptr[i];
}
MatrixView( T* p, std::size_t const* stride ):ptr(p) {}
private:
T* ptr = 0;
};
template<class T, std::size_t N>
struct Matrix {
Matrix( std::array<std::size_t, N> sizes ) {
std::size_t accumulated = 1;
for (std::size_t i = 1; i < sizes.size(); ++i) {
accumulated *= sizes[N-i];
strides[N-i] = accumulated;
}
storage.resize( strides[0] * sizes[0] );
}
MatrixView<T, N> get() { return {storage.data(), strides.data()}; }
MatrixView<T const, N> get() const { return {storage.data(), strides.data()}; }
private:
std::vector<T> storage;
std::array<std::size_t, N-1> strides;
};
this requires doing Matrix<int, 6> m{ {5,4,2,1,3,5} }; to create a matrix with 6 dimensions.
To access it you need to do m.get()[3][0][0][0][0][0] = 4.
You get get rid of that .get() but it is a bit annoying so long as you want to support tensors of first order.
The data is stored contiguously.
Suppose I have this C++ function:
class C { ... };
void do(const vector<C>& cs) {
...
for (...) {
cs[i].do_whatever();
}
...
}
But C is expensive to copy so I might have something like this:
std::vector<C*> reorder_in_some_way(const std::vector<C>& cs) {
...
}
int main() {
std::vector<C> cs = ...;
std::vector<C*> reorderedCs = reorder_in_some_way(cs);
do(reorderedCs);
}
Obviously this won't work. I could get around it by giving up and just making do a template over any type like this:
template<typename T>
void do(const vector<T>& cs) {
But it really only works with C's and I'd like that to be encoded in the type system - and also it makes do() easier to understand if you don't have to go hunting around for places where it is used.
Is there any way to write do() so that it can generically take both vector<C> and vector<C*> (and for bonus points vector<reference_wrapper<C>>)?
Just write 2 template functions that applies a functor:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( i );
}
template<class T,typename Func>
void apply( const std::vector<T*> &v, Func f )
{
for( auto i : v ) f( *i );
}
then pass a lambda:
std::vector<C> vc;
std::vector<C*> vp;
auto call = []( const C &c ) { c.do_whatever(); };
apply( vc, call );
apply( vp, call );
(note you cannot call your function do - it is a keyword in C++)
live example
PS As you mentioned in comments your function apply is rather complex so you prefer to have only one copy of it, in this case create a helper:
template<class T>
const T &apply_helper( const T *t ) { return *t; }
template<class T>
typename std::enable_if<!std::is_pointer<T>::value, const T &>::type
apply_helper( const T &t ) { return t; }
then write your apply function only once:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( apply_helper( i ) );
}
live example N2
You might keep your do function generic, but specialize a getter for T& and T* that both return a T&:
namespace detail{
template<class T>
T& get(T& _in){
return _in;
}
template<class T>
T& get(T* _in){
return *_in;
}
} // namespace detail
template<class T>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
Demo
Either way you are going to need to specialize between pointers and references. I think that this pushes it to the smallest scope.
If you want to constrain do_a_thing to only accept C or C*, we can create a small trait to do this:
template <class T>
struct is_c : std::false_type{};
template <>
struct is_c<C>: std::true_type{};
template <>
struct is_c<C*>: std::true_type{};
And then modify do_a_thing with std::enable_if:
template<class T, std::enable_if_t<is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
For bonus points, we'll write another specialization of do_a_thing that gives a nice compiler error for types that do not satisfy the constraint:
template<class T>
struct always_false : std::false_type{};
template<class T, std::enable_if_t<!is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
static_assert(always_false<T>::value, "do_a_thing only works for C and C*");
}
Now the following will fail:
struct Q{};
std::vector<Q> qs;
do_a_thing(qs); // compiler error
Demo
Write a function template that gets a pair of iterators (not a vector).
Then pass it either normal vector<C>::iterators, or adapted vector<C*>::iterators, e.g. boost::transform_iterator instances.
Working example:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <boost/iterator/transform_iterator.hpp>
int& deref(int*& x) { return *x; }
template <class it>
void print(it from, it to)
{
std::copy(from, to, std::ostream_iterator<typename it::value_type>(std::cout, " "));
std::cout << "\n";
}
int main()
{
std::vector<int> a {4,3,7,1};
std::vector<int*> b {new int(2), new int(0), new int(11), new int(-3)};
// auto deref = [](int*& x) -> int& { return *x; };
// cannot use a lambda here because it's non-copyable
// and iterators must be copyable.
std::sort(std::begin(a), std::end(a));
std::sort(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
print(std::begin(a), std::end(a));
print(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
}
I think a possible solution could be to create a modified vector class that is generic with respect to pointerness, and can be implicitly converted to from a vector<T> or a vector<T*>. Like this:
template<typename T>
class VectorWrapper {
public:
VectorWrapper(const vector<T>& v) : reference(&v) { }
VectorWrapper(const vector<T*>& v) : pointer(&v) { }
const T& at(int idx) const {
if (reference)
return (*reference)[idx];
return *(*pointer)[idx];
}
// etc. for size() and so on. You could probably have
// this class derive from std::vector and reimplement its
// methods to switch between `reference` and `pointer`.
private:
const vector<T>* reference = nullptr;
const vector<T*>* pointer = nullptr;
};
void do_thing(VectorWrapper<C> wrapper) {
wrapper.at(0).whatever();
}
Not tested, and I don't think I'll go this route to be honest but it's the best I could come up with.
Suppose that we have the vector class below which has been shortened to minimum to showcase the question.
template <typename T>
class VectorT : private std::vector<T>
{
using vec = std::vector<T>;
public:
using vec::operator[];
using vec::push_back;
using vec::at;
using vec::emplace_back;
// not sure if this is the beast way to check if my T is really a unique_ptr
template<typename Q = T>
typename Q::element_type* operator[](const size_t _Pos) const { return at(_Pos).get(); }
};
Is there any way to check if T is a unique_ptr and if yes to add an operator[] to return the unique_ptr::element_type*. At the same time though the normal operator[] should also work.
VectorT<std::unique_ptr<int>> uptr_v;
uptr_v.emplace_back(make_unique<int>(1));
//int* p1 = uptr_v[0]; // works fine if using vec::operator[]; is commented out
// then of course it wont work for the normal case
//std::cout << *p1;
VectorT<int*> v;
v.emplace_back(uptr_v[0].get());
int *p2 = v[0];
std::cout << *p2;
Any way to achieve something like that ?
Edited:
The reason I am asking for this is cause I can have say my container
class MyVec: public VectorT<std::unique_ptr<SomeClass>>
but I can also have a
class MyVecView: public VectorT<SomeClass*>
Both classes will pretty much have identical functions. So I am trying to avoid duplication by doing something like
template<typename T>
void doSomething(VectorT<T>& vec)
{
SomeClass* tmp = nullptr;
for (size_t i = 0; i < vec.size(); ++i)
{
tmp = vec[i]; // this has to work though
....
}
}
Then of course I can
MyVec::doSomething(){doSomething(*this);}
MyVecView::doSomething(){doSomething(*this);}
which of course means that the operator[] has to work for both cases
The goal here is to have only one operator[]. Techniques with more than one operator[] violate DRY (don't repeat yourself), and it is hard to avoid having a template method whose body would not compile if instantiated (which, under a strict reading of the standard, could result in your code being ill-formed).
So what I'd do is model the "turn something into a pointer" like this:
namespace details {
template<class T>
struct plain_ptr_t;
//specialzation for T*
template<class T>
struct plain_ptr_t<T*> {
T* operator()(T* t)const{return t;}
};
//specialzation for std::unique_ptr
template<class T, class D>
struct plain_ptr_t<std::unique_ptr<T,D>> {
T* operator()(std::unique_ptr<T>const& t)const{return t.get();}
};
//specialzation for std::shared_ptr
template<class T>
struct plain_ptr_t<std::shared_ptr<T>> {
T* operator()(std::shared_ptr<T>const& t)const{return t.get();}
};
}
struct plain_ptr {
template<class T>
typename std::result_of< details::plain_ptr_t<T>( T const& ) >::type
operator()( T const& t ) const {
return details::plain_ptr_t<T>{}( t );
}
};
now plain_ptr is a functor that maps smart pointers to plain pointers, and pointers to pointers.
It rejects things that aren't pointers. You could change it to just pass them through if you like, but it takes a bit of care.
We then use them to improve your operator[]:
typename std::result_of< plain_ptr(typename vec::value_type const&)>::type
operator[](size_t pos) const {
return plain_ptr{}(at(pos));
}
notice that it is no longer a template.
live example.
template<typename T> struct unique_ptr_type { };
template<typename T> struct unique_ptr_type<std::unique_ptr<T>> { using type = T; };
namespace detail {
template<typename T> std::false_type is_unique_ptr(T const&);
template<typename T> std::true_type is_unique_ptr(std::unique_ptr<T> const&);
}
template<typename T>
using is_unique_ptr = decltype(detail::is_unique_ptr(std::declval<T>()));
template<typename T>
class VectorT : std::vector<T> {
using vec = std::vector<T>;
public:
using vec::at;
using vec::emplace_back;
using vec::push_back;
template<typename Q = T,
typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
Q& operator [](std::size_t pos) { return vec::operator[](pos); }
template<typename Q = T,
typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
Q const& operator [](std::size_t pos) const { return vec::operator[](pos); }
template<typename Q = T,
typename U = typename unique_ptr_type<Q>::type>
U* operator [](std::size_t pos) { return vec::operator[](pos).get(); }
template<typename Q = T,
typename U = typename unique_ptr_type<Q>::type>
U const* operator [](std::size_t pos) const { return vec::operator[](pos).get(); }
};
Online Demo
SFINAE is used to only enable the custom operator[] if T is std::unique_ptr<T>, and to only enable std::vector<T>::operator[] otherwise.
If you insist on plain pointers you could write something like
template<class T> auto plain_ptr(T* p) { return p; }
template<class T> auto plain_ptr(std::unique_ptr<T>& p) { return p.get(); }
and then do
tmp = plain_ptr(vec[i]); // tmp will be SomeClass*
or you could have tmp local:
template<typename T>
void doSomething(VectorT<T>& vec)
{
static_assert(std::is_same<T, SomeClass*>::value ||
std::is_same<T, std::unique_ptr<SomeClass>>::value,
"doSomething expects vectors containing SomeClass!");
for (std::size_t i = 0; i < vec.size(); ++i)
{
auto tmp = vec[i];
// ... (*tmp).foo or tmp->foo ...
}
// or perhaps even
for(auto&& tmp : vec)
{
// ... again (*tmp).foo or tmp->foo ...
}
}
I'm trying to implement a hypercubeclass, that is, multidimensional vectors.
I have a problem generalizing it. I'm able to make one for a three dimensional hypercube, but as mentioned, the problem is generalizing it. Could anyone help me? You should be able to write hypercube<4> w(5) to get 4 dimensions and 5 elements in each vector that is 5*5*5*5 elements in total.
Here is the code I have for the three dimensional version:
#include <vector>
using std::vector;
using namespace std;
template <int b>
class Hypercube {
public:
Hypercube(int a) : intvec(a){
for (int i = 0; i<a;i++) {
intvec[i].resize(a);
for (int j = 0;j<a;j++) {
intvec[i][j].resize(a);
}
}
}
vector<vector<int> >& operator[](int i) {
return intvec[i];
}
vector<vector<vector<int> > > intvec;
};
For this to work, you need recursive inheritence to provide the correct vector type and the initialization function. Both work recursively, for which I created a little helper struct called hcube_info:
// hypercube.h
#include <vector>
template<unsigned N>
struct hcube_info;
template<>
struct hcube_info<1>
{ // base version
typedef std::vector<int> type;
static type init(unsigned innerdim, int value = 0){
return type(innerdim, value);
}
};
template<unsigned N>
struct hcube_info
{ // recursive definition, N dimensions
private:
typedef hcube_info<N-1> base;
typedef typename base::type btype;
public:
typedef std::vector<btype> type;
static type init(unsigned innerdim, int value = 0){
return type(innerdim, base::init(innerdim, value));
}
};
As you can see, recursion all the way to the one dimensional base case. We also need to recursively initialize the vector to pass the inner dimension all the way through.
And now the real class, a nice interface around the hcube_info:
template<unsigned N>
struct hypercube
{
private:
typedef hcube_info<N> info;
typedef typename info::type vec_type;
public:
typedef typename vec_type::value_type value_type;
typedef typename vec_type::size_type size_type;
explicit hypercube(unsigned innerdim, unsigned value = 0)
: c(info::init(innerdim, value))
{
}
value_type& operator[](unsigned i){
return c[i];
}
size_type size() const{ return c.size(); }
private:
vec_type c;
};
Test program:
#include "hypercube.h"
#include <iostream>
int main(){
hypercube<4> c(5);
unsigned s = c.size() * // dim 1
c[0].size() * // dim 2
c[0][0].size() * // dim 3
c[0][0][0].size(); // dim 4
std::cout << s << '\n'; // outputs: 625 -> 5 * 5 * 5 * 5 -> 5^4
}
I would suggest something along those lines:
template <typename T, unsigned dim> class HQ {
std::vector<HQ<T,(dim-1)> > vector;
public:
HQ(unsigned size) : vector(size,HQ<T,(dim-1)>(size)) {}
};
template <typename T> class HQ<T,1> {
std::vector<T> vector;
public:
HQ(unsigned size) : vector(size,T()) {}
};
template <typename T> class HQ<T,0> {};
You can then implement your accessors for the first both templates as you wish. You can also make things a bit more simple and robust by allowing zero-dimensional matrices:
template <typename T, unsigned dim> class HQ {
std::vector<HQ<T,(dim-1)> > vector;
public:
HQ(unsigned size) : vector(size,HQ<T,(dim-1)>(size)) {}
};
template <typename T> class HQ<T,0> {
T data;
public:
HQ(unsigned size) : data() {}
};
I imagine an access operator would look something like this:
template <typename T, unsigned dim> HQ<T,(dim-1)>& HQ<T,dim>::operator[](unsigned i) {
return vector[i];
}
template <typename T, unsigned dim> HQ<T,(dim-1)> const& HQ<T,dim>::operator[](unsigned i) const {
return vector[i];
}
such that you can write
HQ<int,4> hq(5);
hq[1][4][2][0] = 77;