Archived

This topic is now archived and is closed to further replies.

sQuid

Template Metaprograms

Recommended Posts

I''m writing a Tensor class with an element access operator (). () takes as many arguments as the dimension of the Tensor. So for a 2-tensor we''d have
    
inline float& operator(int e1, int e2);
  
whilst for a 3-tensor it would be
      
inline float& operator(int e1, int e2, int e3);
    
and so on. I would like to template (or otherwise) the class so the number of arguments to () can be determined and the appropriate function defined automatically. Is this possible and can anyone provide some ideas on how to do it? thanking you ...

Share this post


Link to post
Share on other sites
Afaik, there is no way to actually specify number of arguments in a function. But here is some code (sort of borrowed from Alexandrescu...) but sort of fakes it. Not sure if that will work for you...


  
template < int N >
class IntArgList {
public:
int head;
IntArgList<N-1> tail;
enum { size = N };

int operator[](int i) {
assert(i >= 0 && "Error i is negative in operator[]");
assert(i < N && "Error i is greater than N-1 in operator[]");
if(i == 0) { return head; }
return tail[i-1];
}
};

template < >
class IntArgList< 2 > {
public:
int head;
int tail;
enum { size = 2 };
int operator[](int i) {
assert(i >= 0 && "Error i is negative in operator[] for IntArgList<2>");
assert(i < 2 && "Error i is greater than N-1 in operator[] for IntArgList<2>");
if(i == 0) { return head; }
return tail;
}
};

template < >
class IntArgList< 1 > {
public:
int head;
enum { size = 1 };
int operator[](int i) {
assert(i == 0 && "Error i is negative in operator[] for IntArgList<1>");
return head;
}
};

template < int N >
class tensor {
float& operator(const IntArgList<N>& args) {... }
};


Regards,
Jeff

[edited by - rypyr on May 5, 2003 12:55:44 AM]

Share this post


Link to post
Share on other sites
Well, one way would be to use BOOST_PP_ (www.boost.org) to do an specialization for the operator() from say 0 to 15 for the whole tensor class. Ugly. But the only "perfect" way IMHO.

  
#define MAKE_TENSOR(z,n,data) \
template<> struct tensor<n> { \
inline float operator()(BOOST_PP_ENUM_PARAMS(n,float e) ) {\
.... \
} \
};

BOOST_PP_REPEAT(15,MAKE_TENSOR,_);

Share this post


Link to post
Share on other sites
I was pretty intrigued by this so I did a little more thinking...


  
#include <iostream>
#include <assert.h>
#include <stdio.h>
#include <stdarg.h>

template< int N >
class IntArgList {
public:
int head;
IntArgList< N-1 > tail;
int& operator[](int i) { return const_cast<int&>(argAt(i)); }
int operator[](int i) const { return argAt(i); }
const int& argAt(int i) const {
assert(i >= 0 && "Error i is < 0 in IntArgList<N>");
assert(i < N && "Error i is > N-1 in IntArgList<N>");
if(i == 0) {
return head;
}
return tail.argAt(i-1);
}
IntArgList<N>() : head(0) {}
IntArgList<N>(int h, ...) : head(h) {
va_list vl;
va_start( vl, h );
for(int loop = 0; loop < N-1; ++loop) {
tail[loop] = va_arg( vl, int );
}
va_end( vl );
}
};

template< >
class IntArgList< 2 > {
public:
int head;
int tail;
int& operator[](int i) { return const_cast<int&>(argAt(i)); }
int operator[](int i) const { return argAt(i); }
const int& argAt(int i) const {
assert(i >= 0 && "Error i is < 0 in IntArgList<N>");
assert(i < 2 && "Error i is > N-1 in IntArgList<2>");
if(i == 0) {
return head;
}
return tail;
}
IntArgList<2>() : head(0), tail(0) {}
IntArgList<2>(int h, int t) : head(h), tail(t) {}
};

template< >
class IntArgList< 1 > {
public:
int head;
int& operator[](int i) { return const_cast<int&>(argAt(i)); }
int operator[](int i) const { return argAt(i); }
const int& argAt(int i) const {
assert(i == 0 && "Error i is != 0 in IntArgList<1>");
return head;
}
IntArgList<1>() : head(0) {}
IntArgList<1>(int h) : head(h) {}
};

template< int N >
class IntVector { // replace this with approp. tensor code

public:
int fields[N];
void initialize( const IntArgList<N>& args ) {
for(int loop = 0; loop < N; ++loop ) {
fields[loop] = args[loop];
}
}
void log() {
for(int loop = 0; loop < N; ++loop ) {
std::cout << "Field #" << loop << " = " << fields[loop] << std::endl;
}
}
};

// this pragma enforces the user to use the right # of arguments

#pragma warning( error: 4002 )
#define ARG1(x1) IntArgList<1>(x1)
#define ARG2(x1,x2) IntArgList<2>(x1,x2)
#define ARG3(x1,x2,x3) IntArgList<3>(x1,x2,x3)
#define ARG4(x1,x2,x3,x4) IntArgList<4>(x1,x2,x3,x4)
#define ARG5(x1,x2,x3,x4,x5) IntArgList<5>(x1,x2,x3,x4,x5)
#define ARG6(x1,x2,x3,x4,x5,x6) IntArgList<6>(x1,x2,x3,x4,x5,x6)
#define ARG7(x1,x2,x3,x4,x5,x6,x7) IntArgList<7>(x1,x2,x3,x4,x5,x6,x7)
#define ARG8(x1,x2,x3,x4,x5,x6,x7,x8) IntArgList<8>(x1,x2,x3,x4,x5,x6,x7,x8)
// generate more if you need them...


int main(int argc, char* argv[])
{
IntVector<3> vec3;
vec3.initialize(ARG3(4,8,16,32)); // generates a compiler error

vec3.initialize(ARG4(4,8,16,32)); // generates a compiler error

vec3.initialize(ARG3(4,8,16)); // OK!

vec3.log();

return 0;
}


If you find the use of the macros ugly, you can still use:

vec3.initialize(IntArgList<3>(4,8,16));

However, you won''t get the benefit of the compiler error if you do this:

vec3.initialize(IntArgList<3>(4,8,16,32)); // compiles without warning

This is one case where I found the preprocessor very useful in "enforcing" my rules.

Now I realize that the above approach may just be overkill, so you could do something like this:


  
template< int N >
class Tensor {
float operator()(int x1, ...) {
// grab the variable arguments, similar to

// my IntArgList<N> constructor approach above

}
};

template< >
class Tensor<1> {
float operator()(int x1) {
}
};


The benefit of this second approach is that you don''t create any temporary objects (for the IntArgList), it will compile much smaller. The disadvantage to this approach is that there is nothing preventing someone from doing:

Tensor<3> tens3;
float f = tens3(4, 8, 16, 32, 64, 128);

Although I suppose you could use the similar ARGx macro approach above without any extra runtime cost.

Regards,
Jeff

Share this post


Link to post
Share on other sites
Wow, I am a silly person. I''m not sure why I wanted to use template recursion so much with my IntArgList template class above...the obvious way to implement this is:


  
template< unsigned int N >
class IntArgList {
public:
int args[N];
int& operator[](int i) { return args[i]; } // syntactic sugar

};


Regards,
Jeff

Share this post


Link to post
Share on other sites