I am implementing an n-dimensional array class which is a template as follows (Note that the data is stored in a linear array whose length is the product of all the dimensions):
template< class valType, int rank >
class NDimensionalArray
{
public:
private:
valType* m_data;
int* m_dimensions;
int m_rank;
};
So the idea is that a user (me) can specify an array of rank 2, and of a certain dimension, ie:
NDimensionalArray<double,2> matrix(10,10);
Now the difficulty is in specializing constructors for 1->n dimensions, each constructor takes n parameters where n is the rank of the array. Now I thought of using a valarray like is used in printf(), however with this defining a 1-dimensional array with 2 dimensions ie:
NDimensionalArray<double,1> matrix(10,10);
would be perfectly acceptable behavior. Is there some neat trick I can use to let the compiler do the repetition? Realistically so long as I know the rank, and have the length of each dimension the constructor can be generic:
{
int nElements = m_dimensions[0];
for ( int i=1 ; i<m_rank ; ++i )
nElements *= m_dimensions[i];
m_data = new valType[nElements];
}
Edit: Note that a similar operation will be needed for accessors.
Also I have considered the option of a constructor which looks like:
NDimensionalArray( const NDimensionalArray<int,1>& dimensions );
Which could be used like:
NDimensionalArray<int,1> dimVec(2); // Need a specification for 1-dimensional arrays.
dimVec(0) = 10;
dimVec(1) = 10;
NDimensionalArray<double,2> matrix(dimVec);
This would be a viable solution, but its ugly compared to the use I would like. Also accessing multi-dimensional arrays would become a serious pain, and seriously slow having to construct a dimension vector for each access.
Okay, I've played with this for a while. Here's some template metaprogramming hackery that does something close to what you want. It lets you specify all dimensions inline, it doesn't do any dynamic memory allocation or other such things. In addition, with a good C++ compiler (I tested with VC++ /O2 option), the code will be fully inlined, with no copies done (in fact, for me it inlined the whole NDimensionalArray constructor at the point of the call). It will typecheck completely at compile-time, and won't let you pass too few or too many dimensions. And it can be reused for indexers. Here goes:
template<class T, int N>
class value_pack : private value_pack<T, N-1>
{
public:
enum { size = N };
value_pack(value_pack<T, N-1> head, const T& tail)
: value_pack<T, N-1>(head)
, value(tail)
{
}
value_pack<T, N+1> operator() (const T& tail) const
{
return value_pack<T, N+1>(*this, tail);
}
template<int I>
const T& get() const
{
return this->value_pack<T, I+1>::value;
}
protected:
const T value;
};
template<class T>
struct value_pack<T, 0>
{
};
struct
{
template <class T>
value_pack<T, 1> operator() (const T& tail) const
{
return value_pack<T, 1>(value_pack<T, 0>(), tail);
}
} const values;
template <class ValType, int Rank>
struct NDimensionalArray
{
NDimensionalArray(value_pack<ValType, Rank> values)
{
// ...
}
};
int main()
{
NDimensionalArray<int, 3> a(values(1)(2)(3));
}
I think the best solution is to take a vector of ints and let the constructor validate it against the template parameter 'rank'.
NDimensionalArray matrix(std::vector<int> matrixDimensions)
{
if (matrixDimensions.size() != rank)
{
throw SomeException();
}
...
}
I don't think any compiler trick can be an alternative here. (Except perhps using macros, if you can think of something, although that wouldn't be a compiler trick strictly speaking.)
Not a direct answer, but check out the blitz library.
There's no good way to do it in C++ as currently standardized. In C++0x, you'll be able to use template parameter packs to approximate (I think I've got the syntax right, but not sure about expansion in requires):
template <class ValType, int Rank>
struct NDimensionalArray
{
template <class... Args>
requires std::SameType<Args, ValType>... && std::True<sizeof...(Args) == Rank>
NDimensionalArray(Args... values)
{
...
}
};
You could take a std::tr1::array. Hmm:
#include <array>
template< class valType, int rank >
class NDimensionalArray
{
public:
NDimensionalArray(const std::tr1::array<rank>& dims);
// ...
};
NDimensionalArray<double,2> foo({10,10});
NDimensionalArray<double,2> bar({10}); // second dimension would be 0
NDimensionalArray<double,1> baz({10,10}); // compile error?
I'm not actually sure if that works! I'll run it through comeau.
Edited As per the comments, looks like this approach would look more like:
std::tr1::array<2> dims = {10, 10};
NDimensionalArray<double,2> foo(dims);
Boost has a multi-array library that uses a custom object for constructing their multidimensional array. It's a really good way to do it; I suggest you study (or better yet, use) their code.
Related
I have a Vector class which has a template of <unsigned int Dim>, eg. I can do Vector<2> for a vector in 2-dimensional space, Vector<4> for 4-dimensional, etc. I want to add some methods to the class if Dim == specific value, for instance CrossProduct for Vector<3>, or x,y,z,w getters for a vector of sufficient dimension. If used with a Vector of incorrect dimensions, I would hope to get a compile error.
I have looked around quite a lot, one of the things that I believe are close enough is std::enable_if, however, I have no clue how to use it for my particular case (where the condition is either Dim == x, or Dim > x).
Is this the right way, or is there a completely different way I should do this?
I am using C++17, by the way.
pre-C++20, static_assert might be the simplest:
template <std::size_t Dims>
struct Vector
{
// ...
double getZ() const { static_assert(Dims >= 3); return data[2]; }
};
SFINAE is possible, but complex and verbose:
template <std::size_t Dims>
struct Vector
{
// ...
template <std::size_t D = Dims, std::enable_if_t<(D >= 3) && D == Dims, int> = 0>
double getZ() const { return data[2]; }
};
Specialization is possible, but might be tricky, for example:
struct NullVector3{};
template <typename Derived>
struct Vector3
{
// ...
double getZ() const { return static_cast<Derived*>(this)->data[2]; }
};
template <std::size_t Dims>
struct Vector : std::conditional_t<(Dims >= 3), Vector3<Vector<Dims>>, NullVector3> /*, ..*/
{
// ...
// inherit of Vector3<Vector>::getZ() when Dims >= 3
// inherit of no extra method from NullVector3 else.
};
C++20 is the simplest with requires:
template <std::size_t Dims>
struct Vector
{
// ...
double getZ() const requires(Dims >= 3) { return data[2]; }
};
In C++20 you may use Constraints and Concepts.
With C++17, you need to emulate them with std::enable_if. Alternatively you could use static_assert, but I would deem enable_if to express the intention better (automatic tools like code-completion should figure enable_if out, but most probably not a static assert).
A practical example is given in this answer as found by #JHBonarius.
I would implement the methods for the generic interface and add
static_assert(Dim==3,"This method is only valid for 3-dimensional vectors.")
as the first line in each method. This will yield much cleaner compiler error message compared to std::enable_if approach.
You can also use if constexpr(Dim==x) for the rest of the method's body and to "specialize" the code based on the current dimension if varying implementations are needed.
The rationale behind said array is to emulate a 2D/3D pixel matrix.
After doing a fair amount of research and reading, I reckon Boost.MultiArray might come in handy for this. However, I still have to create a neat wrapper on top of it to allow for less verbose coding.
Ultimately, what I want to achieve is the following:
PixMat<u8, 3, {2, 4, 3}> pixMat;
or
PixMat<u8, 3> pixMat(2,3,4);
, which would basically create a 2x4x3 matrix of u8 values.
What I've come up with so far is:
template <typename T, int Dims>
class PixMat {
public:
typedef typename boost::multi_array<T, Dims> PixMatType;
typedef typename PixMatType::index PixMatTypeIdx;
PixMat(int dim1Ext, int dim2Ext) : pixMat(PixMatType(boost::extents[dim1Ext][dim2Ext])) {}
PixMat(int dim1Ext, int dim2Ext, int dim3Ext) : pixMat(PixMatType(boost::extents[dim1Ext][dim2Ext][dim3Ext])) {}
private:
PixMatType pixMat;
};
template <typename T>
class Pix2DMat : PixMat<T, 2> {
public:
Pix2DMat(int dim1Ext, int dim2Ext) : PixMat<DataType, 2>(dim1Ext, dim2Ext) {}
};
template <typename T>
class Pix3DMat : PixMat<T, 3> {
public:
Pix3DMat(int dim1Ext, int dim2Ext, int dim3Ext) : PixMat<DataType, 3>(dim1Ext, dim2Ext, dim3Ext) {}
};
I'm not too keen on this solution. Normally, the matrix won't be either 2D or 3D, but still, I'd like a more generic solution.
Questions:
Is there a way to provide the extents of the dimensions as template arguments as well instead of via C-TOR?
Is there a better way than inheritance to achieve this e.g. template specialization, variadic templates? But then how to deal with not duplicating the typedefs for boost all over the place?
Here are a few techniques that I think you could use:
Variadic constructor argument
Rather than having a separate constructor for each possible dimension, you could use variadic argument techniques to create a generic N-dimensional constructor. Something that is your friend here: boost::extents is not required for the constructor argument, but instead anything that meets the requirements of a Collection. One example is just a plain STL or boost array:
template <typename... DimSizes>
PixMat(DimSizes&&... sizes)
: pixMat(boost::array<std::size_t, sizeof...(DimSizes)>{{ static_cast<size_t>(sizes)...}}) {
}
This isn't the most polished implementation; in particular it doesn't place much of a requirement on DimSizes, which should really all be the same unsigned integer type (see this question for possible improvements). Also (for simplicity) perfect forwarding isn't implemented, but that probably just requires wrapping sizes with std::forward<DimSizes>(sizes) in the constructor.
You can consult this stackoverflow post for possible alternative implementations.
Static assertion / SFINAE
Your template base class has a 2D constructor and 3D constructor --- or if you follow the above, a template N-dimensional constructor --- regardless of the value of the actual template parameter. You could use static assertion or SFINAE so that only the the Dims-D dimensional constructor is compilable. This will convert run-time bugs into compilation errors:
template <typename... DimSizes>
PixMat(DimSizes&&... sizes)
: pixMat(boost::array<std::size_t, sizeof...(DimSizes)>{{ static_cast<size_t>(sizes)...}}) {
static_assert(sizeof...(DimSizes) == Dims);
}
Dimension sizes as templates
I think this is a possible though completely orthogonal solution. It's implementation would follow from the above without too much work, but to make this interoperable with the constructor argument based solution would require a lot of hard work (i.e. to make them part of the same class or class hierarchy).
Other libraries
You might want to take a look at Eigen, for example. It does a lot of the aforementioned hard work.
If I understood you correctly, you want compile-time dimension, but run-time extents.
I would use a design like this:
template <typename T,std::size_t Dim>
class mdvector
{
private:
std::vector<T> Data;
std::array<std::size_t,Dim> Extents;
private:
std::size_t Offset(std::size_t const Sum) const
{ return Sum; }
template <typename... IndexType>
std::size_t Offset(std::size_t const Sum,std::size_t const Index,IndexType const... Indices) const
{ return Offset(Sum*Extents[Dim-sizeof...(Indices)-1u]+Index,Indices...); }
public:
template <typename... IndexType,typename= std::enable_if_t<sizeof...(IndexType)==Dim>>
mdvector(IndexType const... Indices):
Data((... * Indices)), // c++17 fold expression
Extents{Indices...} {}
template <typename... IndexType,typename= std::enable_if_t<sizeof...(IndexType)==Dim>>
T const &operator()(IndexType const... Indices) const
{ return Data[Offset(0u,Indices...)]; }
template <typename... IndexType,typename= std::enable_if_t<sizeof...(IndexType)==Dim>>
T &operator()(IndexType const... Indices)
{ return Data[Offset(0u,Indices...)]; }
};
The data are stored in a std::vector while the extents make up an std::array.
Since I assume you want multi-dimensional access I have used a given mapping via the recursive function Offset. You have quite freedom in this regard.
Here's an example of use:
int main()
{
mdvector<double,3u> myvec(2u,3u,4u);
for (int i= 0; i<2; ++i)
for (int j= 0; j<3; ++j)
for (int k= 0; k<4; ++k)
myvec(i,j,k)= i;
for (int k= 0; k<4; ++k)
{
for (int i= 0; i<2; ++i)
{
for (int j= 0; j<3; ++j)
std::cout << myvec(i,j,k) << "\t";
std::cout << "\n";
}
std::cout << "\n";
}
}
Since the extents are dynamic, you can change them at runtime, for example:
template <typename T,std::size_t Dim>
template <typename... IndexType,typename= std::enable_if_t<sizeof...(IndexType)==Dim>>
void mdvector<T,Dim>::Resize(IndexType const... Indices)
{ Data.resize((... * Indices)); Extents= {Indices...}; }
If, on the other hand, you prefer the extents to be template parameters, they should stay fixed at runtime. In that case, you would include them in the class declaration:
template <typename T,std::size_t... Indices>
class mdvector { /* ... */};
but the implementation would be pretty much the same. Note that specifying the dimension would be unnecessary since you can get it with sizeof...(Indices).
The fold expression in the above code is not estrictly necessary. An equivalent outcome is achieved with:
static constexpr std::size_t Product()
{ return 1u; }
template <typename... IndexType>
static constexpr std::size_t Product(std::size_t const Index,IndexType const... Indices)
{ return Index*Product(Indices...); }
template <typename... IndexType,typename= std::enable_if_t<sizeof...(IndexType)==Dim>>
mdvector(IndexType const... Indices):
Data(Product(Indices...)), Extents{Indices...} {}
I'd like to make the function multi_dimensional accept a multidimensional array by reference.
Can this be done with a variation of the syntax below which works for three_dimensional?
#include <utility>
// this works, but number of dimensions must be known (not variadic)
template <size_t x, size_t y, size_t z>
void three_dimensional(int (&nd_array)[x][y][z]) {}
// error: parameter packs not expanded with ‘...’
template <size_t... dims>
void multi_dimensional(int (&nd_array)[dims]...) {}
int main() {
int array[2][3][2] = {
{ {0,1}, {2,3}, {4,5} },
{ {6,7}, {8,9}, {10,11} }
};
three_dimensional(array); // OK
// multi_dimensional(array); // error: no matching function
return 0;
}
The main problem is that you cannot make the number of array dimensions itself variadic. So whichever way you go, you will almost certainly need a recursive approach of some sort to deal with the individual array layers. What exactly such approach should look like will mainly depend on what exactly you're planning to do with the array once it's been given to you.
If really all you want is a function that can be given any multi-dimensional array, then just write a function that can be given anything but only exists as long as that anything is an array:
template <typename T>
std::enable_if_t<std::is_array_v<T>> multi_dimensional(T& a)
{
constexpr int dimensions = std::rank_v<T>;
// ...
}
However, this by itself will most likely not get you very far. To actually do anything meaningful with the array you've been given, you will most likely need some recursive walking through subarrays. Unless you really just want to look at the topmost layer of the structure.
Another approach is to use a recursive template to peel back the individual array levels, for example:
// we've reached the bottom
template <typename T, int N>
void multi_dimensional(T (&a)[N])
{
// ...
}
// this matches any array with more than one dimension
template <typename T, int N, int M>
void multi_dimensional(T (&a)[N][M])
{
// peel off one dimension, invoke function for each element on next layer
for (int i = 0; i < N; ++i)
multi_dimensional(a[i]);
}
I would, however, suggest to at least consider using std::array<> instead of raw arrays as the syntax and special behavior of raw arrays tends to turn everything into a confusing mess in no time. In general, it might be worth to implement your own multi-dimensional array type, like an NDArray<int, 2, 3, 2> which internally works with a flattened representation and just maps multi-dimensional indices to a linear index. One advantage of this approach (besides the cleaner syntax) would be that you can easily change the mapping, e.g., to switch from row-major to column-major layout, e.g., for performance optimization…
To implement a general nD array with static dimensions, I would introduce a helper class to encapsulate the recursive computation of a linear index from an nD index:
template <std::size_t... D>
struct row_major;
template <std::size_t D_n>
struct row_major<D_n>
{
static constexpr std::size_t SIZE = D_n;
std::size_t operator ()(std::size_t i_n) const
{
return i_n;
}
};
template <std::size_t D_1, std::size_t... D_n>
struct row_major<D_1, D_n...> : private row_major<D_n...>
{
static constexpr std::size_t SIZE = D_1 * row_major<D_n...>::SIZE;
template <typename... Tail>
std::size_t operator ()(std::size_t i_1, Tail&&... tail) const
{
return i_1 + D_1 * row_major<D_n...>::operator ()(std::forward<Tail>(tail)...);
}
};
And then:
template <typename T, std::size_t... D>
class NDArray
{
using memory_layout_t = row_major<D...>;
T data[memory_layout_t::SIZE];
public:
template <typename... Args>
T& operator ()(Args&&... args)
{
memory_layout_t memory_layout;
return data[memory_layout(std::forward<Args>(args)...)];
}
};
NDArray<int, 2, 3, 5> arr;
int main()
{
int x = arr(1, 2, 3);
}
I think the most simple way to ask is due to an example. Assume we have the following type:
class Node
{
// make noncopyable
Node(const Node& ref) = delete;
Node& operator=(const Node& ref) = delete;
// but moveable
Node(Node&& ref) = default;
Node& operator=(Node&& ref) = default;
// we do not have a default construction
Node() = delete;
Node(unsigned i): _i(i) {}
unsigned _i;
};
Now i want to store some of these Nodes in a std::array:
template<unsigned count>
class ParentNode
{
std::array<Node,count> _children;
ParentNode()
// i cannt do this, since i do not know how many nodes i need
// : _children{{Node(1),Node(2),Node(3)}}
: _children() // how do i do this?
{}
};
As stated in the comment, the question is: How do I do this? The unsigned passed to the child should be the index of the array where the child is stored. But more general solutions are also very appreciated!
The following solution I found myself might end up in undefined behavior for more complex types. For a proper well defined solution see accepted answer.
template<unsigned count>
class ParentNode
{
public:
// return by value as this will implicitly invoke the move operator/constructor
std::array<Node,count> generateChildren(std::array<Node,count>& childs)
{
for (unsigned u = 0; u < count; u++)
childs[u] = Node(u); // use move semantics, (correct?)
return std::move(childs); // not needed
return childs; // return by value is same as return std::move(childs)
}
std::array<Node,count> _children;
ParentNode()
// i cannt do this, since i do not know how many nodes i need
// : _children{{Node(1),Node(2),Node(3)}}
: _children(generateChildren(_children)) // works because of move semantics (?)
{}
};
ParentNode<5> f;
The code does compile. But I am not sure if it does what i expect it to do. Maybe someone who has good insight in move semantics and rvalue references can just add some comments:-)
You can use a variadics to generate an array with elements initialized to an arbitrary function of the indices. Using the standard machinery for generating index sequences:
template <int... I> struct indices {};
template <int N, int... I> struct make_indices :
make_indices<N-1,N-1,I...> {};
template <int... I> struct make_indices<0,I...> : indices<I...> {};
it's fairly straightforward:
template <typename T, typename F, int... I>
inline std::array<T, sizeof...(I)> array_maker(F&& f, indices<I...>) {
return std::array<T, sizeof...(I)>{ std::forward<F>(f)(I)... };
}
template <typename T, std::size_t N, typename F>
inline std::array<T, N> array_maker(F&& f) {
return array_maker<T>(std::forward<F>(f), make_indices<N>());
}
Which lets us do anything from duplicating the effect of std::iota:
auto a = array_maker<int,10>([](int i){return i;});
to making an array with the squares of the first 10 natural numbers in reverse order:
const auto a = array_maker<std::string,10>([](int i){
return std::to_string((10 - i) * (10 - i));
});
Since your Node is movable, this allows you to define your ParentNode constructor as:
ParentNode()
: _children(array_maker<Node, count>([](unsigned i){return i+1;}))
{}
See it all put together live at Coliru.
Really, there's not much you can do. You painted yourself into a corner by wanting to put a type with no default constructor into an array of a size determined by a template parameter, and then wanting to initialize the elements with some arbitrary values.
There is nothing you can return from a function which can be put into a braced-init-list and used to initialize an array (or aggregate of any kind) with more than one element. {} doesn't mean "initializer_list". It's a braced-init-list, which can become an initializer_list under certain circumstances, but it can also become the parameters for a constructor call or the elements to use in aggregate initialization.
Your best bet is really to just use a vector and initialize it manually with a loop.
I would like to know if it is possible to have sort of compile time loops.
For example, I have the following templated class:
template<class C, int T=10, int B=10>
class CountSketch
{
public:
CountSketch()
{
hashfuncs[0] = &CountSketch<C>::hash<0>;
hashfuncs[1] = &CountSketch<C>::hash<1>;
// ... for all i until i==T which is known at compile time
};
private:
template<int offset>
size_t hash(C &c)
{
return (reinterpret_cast<int>(&c)+offset)%B;
}
size_t (CountSketch::*hashfuncs[T])(C &c);
};
I would thus like to know if I can do a loop to initialize the T hash functions using a loop. The bounds of the loops are known at compile time, so, in principle, I don't see any reason why it couldn't be done (especially since it works if I unroll the loop manually).
Of course, in this specific example, I could just have made a single hash function with 2 parameters (although it would be less efficient I guess). I am thus not interested in solving this specific problem, but rather knowing if "compile time loops" existed for similar cases.
Thanks!
Nope, it's not directly possible. Template metaprogramming is a pure functional language. Every value or type defined through it are immutable. A loop inherently requires mutable variables (Repeatedly test some condition until X happens, then exit the loop).
Instead, you would typically rely on recursion. (Instantiate this template with a different template parameter each time, until you reach some terminating condition).
However, that can solve all the same problems as a loop could.
Edit: Here's a quick example, computing the factorial of N using recursion at compile-time:
template <int N>
struct fac {
enum { value = N * fac<N-1>::value };
};
template <>
struct fac<0> {
enum { value = 1 };
};
int main() {
assert(fac<4>::value == 24);
}
Template metaprogramming in C++ is a Turing-complete language, so as long as you don't run into various internal compiler limits, you can solve basically any problem with it.
However, for practical purposes, it may be worth investigating libraries like Boost.MPL, which contains a large number of data structures and algorithms which simplify a lot of metaprogramming tasks.
Yes. Possible using compile time recursion.
I was trying with your code but since it was not compilable here is a modified and compiling exmaple:
template<class C, int T=10>
class CountSketch
{
template<int N>
void Init ()
{
Init<N-1>();
hashfuncs[N] = &CountSketch<C>::template hash<N>;
cout<<"Initializing "<<N<<"th element\n";
}
public:
CountSketch()
{
Init<T>();
}
private:
template<int offset>
size_t hash(C &c)
{
return 0;
}
size_t (CountSketch::*hashfuncs[T])(C &c);
};
template<>
template<>
void CountSketch<int,10>::Init<0> ()
{
hashfuncs[0] = &CountSketch<int,10>::hash<0>;
cout<<"Initializing "<<0<<"th element\n";
}
Demo. The only constraint of this solution is that you have to provide the final specialized version as, CountSketch<int,10>::Init<0> for whatever type and size.
You need a combination of boost::mpl::for_each and boost::mpl::range_c.
Note: This will result in run-time code and this is what you actually need. Because there is no way to know the result of operator& at compile time. At least none that I'm aware of.
The actual difficulty with this is to build a struct that is templated on an int parameter (mpl::int_ in our case) and that does the assignment when operator() is called and we also need a functor to actually capture the this pointer.
This is somewhat more complicated than I anticipated but it's fun.
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/copy.hpp>
// aforementioned struct
template<class C, class I>
struct assign_hash;
// this actually evaluates the functor and captures the this pointer
// T is the argument for the functor U
template<typename T>
struct my_apply {
T* t;
template<typename U>
void operator()(U u) {
u(t);
}
};
template<class C, int T=10, int B=10>
class CountSketch
{
public:
CountSketch()
{
using namespace boost::mpl;
// we need to do this because range_c is not an ExtensibleSequence
typedef typename copy< range_c<int, 0, T>,
back_inserter< vector<> > >::type r;
// fiddle together a vector of the correct types
typedef typename transform<r, typename lambda< assign_hash<C, _1 > >::type >
::type assignees;
// now we need to unfold the type list into a run-time construct
// capture this
my_apply< CountSketch<C, T, B> > apply = { this };
// this is a compile-time loop which actually does something at run-time
for_each<assignees>(apply);
};
// no way around
template<typename TT, typename I>
friend struct assign_hash;
private:
template<int offset>
size_t hash(C& c)
{
return c;
// return (reinterpret_cast<int>(&c)+offset)%B;
}
size_t (CountSketch::*hashfuncs[T])(C &c);
};
// mpl uses int_ so we don't use a non-type template parameter
// but get a compile time value through the value member
template<class C, class I>
struct assign_hash {
template<typename T>
void operator()(T* t) {
t->hashfuncs[I::value] = &CountSketch<C>::template hash<I::value>;
}
};
int main()
{
CountSketch<int> a;
}
with C++20 and consteval compile time loops became possible without doing template hell unless the value can have multiple types:
consteval int func() {
int out = 0;
for(int i = 10; i--;) out += i;
return out;
}
int main() {
std::cout << func(); // outputs 45
}
There are compilers that will see the loop and unroll it. But it's not part of the language specification that it must be done (and, in fact, the language specification throws all sorts of barriers in the way of doing it), and there's no guarantee that it will be done, in a particular case, even on a compiler that "knows how".
There are a few languages that explicitly do this, but they are highly specialized.
(BTW, there's no guarantee that the "unrolled" version of your initializations would be done "at compile time" in a reasonably efficient fashion. But most compilers will, when not compiling to a debug target.)
Here is, I think, a better version of the solution given above.
You can see that we use the compile-time recursive on the function params.
This enables putting all the logic inside your class, and the base case of Init(int_<0>) is very clear - just do nothing :)
Just so you won't fear performance penalty, know that the optimizer will throw away these unused parameters.
As a matter of fact, all these function calls will be inlined anyway. that's the whole point here.
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <iostream>
using namespace std;
template <class C, int N = 10, int B = 10>
class CountSketch {
public:
CountSketch() {
memset(&_hashFunctions, sizeof(_hashFunctions), 0); // for safety
Init(int_<N>());
}
size_t HashAll(C& c)
{
size_t v = 0;
for(const auto& h : _hashFunctions)
{
v += (this->*h)(c); // call through member pointer
}
return v;
}
private:
template<int offset>
size_t hash(C &c)
{
return (reinterpret_cast<size_t>(&c)+offset)%B;
}
size_t (CountSketch::*_hashFunctions[N])(C &c);
private: // implementation detail
// Notice: better approach.
// use parameters for compile-time recursive call.
// you can just override for the base case, as seen for N-1 below
template <int M>
struct int_ {};
template <int M>
void Init(int_<M>) {
Init(int_<M - 1>());
_hashFunctions[M - 1] = &CountSketch<C, N, B>::template hash<M>;
printf("Initializing %dth element\n", M - 1);
}
void Init(int_<0>) {}
};
int main() {
int c;
CountSketch<int, 10> cs;
int i;
cin >> i;
printf("HashAll: %d", cs.HashAll(c));
return 0;
}
Compiler Explorer