Jump to content
  • Advertisement
Sign in to follow this  
Trenki

C++ template template parameters

This topic is 4106 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Consider the following code fragment:
template <template <typename T> class C, int count>
struct operators {
	// compiler gives errors saying T was not declared in this scope
	operator const T* () const { return reinterpret_cast<const T*>(this); }
	operator T* () { return reinterpret_cast<T*>(this); }
	
	// Do I need to use C<T> here?
	C& operator += (const C& rhs) { for (int i = 0; i < count; ++i) (*this) += rhs; return *this; }
	C& operator -= (const C& rhs) { for (int i = 0; i < count; ++i) (*this) -= rhs; return *this; }
	C& operator *= (const C& rhs) { for (int i = 0; i < count; ++i) (*this) *= rhs; return *this; }
	C& operator /= (const C& rhs) { for (int i = 0; i < count; ++i) (*this) /= rhs; return *this; }
	
	C& operator += (const T& rhs) { for (int i = 0; i < count; ++i) (*this) += rhs; return *this; }
	C& operator -= (const T& rhs) { for (int i = 0; i < count; ++i) (*this) -= rhs; return *this; }
	C& operator *= (const T& rhs) { for (int i = 0; i < count; ++i) (*this) *= rhs; return *this; }
	C& operator /= (const T& rhs) { for (int i = 0; i < count; ++i) (*this) /= rhs; return *this; }
};

Why does the compiler not recognise the T? Do I have to use C<T> inside the class body or is C only enough? Also is it safe to cast the this pointer? All the derived classes will have their elements laid out one after the other so casting the this pointer should give me a pointer to the first of those elements. What do I have to do to make the above code work?

Share this post


Link to post
Share on other sites
Advertisement
Yes, you would have to use C<T>.

I have no idea what you're trying to do, but my mind is screaming your code is so wrong. Those casts are horrendous.

What are you actually trying to accomplish?

Share this post


Link to post
Share on other sites
I want to restructure my vector_math libray and to reduce code duplication I would like to factor out all the common operation. And as every class has those common operators I though I could put them in a base class.

Share this post


Link to post
Share on other sites
Probably not the best way to go about it. If you're using separate classes for the dimensions (vector2d, vector3d, etc), then that code duplication is allowable, and preferred over what you're proposing. If you're really worried about it, maybe template your vector class to allow for N dimensions and elements of type E (whatever). This will reduce duplication, increase the flexibility of the code, and give you a good grounding in the eccentricities of templates. You can do it with your matrix class too (bonus points for creating functions that only accept certain specializations of those classes).

Share this post


Link to post
Share on other sites
Having the vector and matrix sizes as a template parameter would be a nice idea. But I want to be able to access the elements of the vectors with v.x, v.y, v.z and v.w and this is incompatible with the templated version.

Share this post


Link to post
Share on other sites
template <typename T> C defines C as a class template, where T is a formal parameter. To illustrate my point, here's an example.

template <typename T> class C;

// C is a class template: therefore, i can use it as an argument to your
// 'operators' template without specifying what T is.
operators<C,0> {};


A better approach would be to use local typedefs, as in the standard library:

template<typename Derived, int count>
struct operators
{
typedef typename Derived::value_type value_type;

// Below, you use 'Derived' where you used C, and 'value_type' where
// you used T.
};

struct Derived : public operators<Derived,0>
{
typedef float value_type;
};



Aside from this, your casts are severely unsafe. It would be preferable to let the derived class provide the base class with a function which returns an array of coordinates.

Share this post


Link to post
Share on other sites
Quote:
Original post by Trenki
Having the vector and matrix sizes as a template parameter would be a nice idea. But I want to be able to access the elements of the vectors with v.x, v.y, v.z and v.w and this is incompatible with the templated version.

Wrong. All you have to do, is write one generic template like this:

template< typename T, unsigned int D > struct Storage {
public:
T operator[] (unsigned int i) const {
assert(i < D);
return data;
}
T & operator[] (unsigned int i) {
assert(i < D);
return data;
}
protected:
T data[D];
};

Now, you can partially specialize the template for as many dimensions as you want, here's the example of how it might look for the dimension of 2:

template< typename T > struct Storage< T, 2 > {
private:
typedef T* (Storage< T, 2 >::* memptr);
protected:
static const memptr data[2];
public:
T operator[] (unsigned int i) const {
assert(i < 2);
return *(this->*data);
}
T & operator[] (unsigned int i) {
assert(i < 2);
return *(this->*data);
}
T x, y;
};
template< typename T >
const typename Storage< T, 2 >::memptr Storage< T, 2 >::data = {
Storage< T, 2 >::x, Storage< T, 2 >::y
};


Having done all that, just make your vector inherit form the storage like this:

template< typename T, unsigned int D > class Vector : public Storage< T, D > {
}



That will let you have the templated vector class and you will still have the ability to access the vector like this: v.x, v.y etc.

All you need is to experiment a little with the templates. That's all. ;)

Share this post


Link to post
Share on other sites
So basically you are proposing something like the following:


template <typename Derived, int count>
struct operators {
typedef Derived::value_type value_type;

Derived& operator += (const Derived& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this) -= rhs;
return static_cast<Derived&>(*this);
}

Derived& operator += (const value_type& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this) += rhs;
return static_cast<C<T>&>(*this);
}
};



This would make the derived class provide a pointer to the elements and also the value_type typedef.

But I probaly could also use this:

template <template <typename T> class C, typename T, int count>
struct operators {
Derived& operator += (const Derived& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this) -= rhs;
return static_cast<Derived&>(*this);
}

Derived& operator += (const T& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this) += rhs;
return static_cast<C<T>&>(*this);
}
};



And therefore get rid of the value_type typedef.

About the unsafe cast: I'm sure it would give the expected behavior for my case as all the derived classes will have their elements laid out one after the other by defining the member variables one after the other. There will be no virtual functions involved and therefore no vtable pointer generated and thus the sizeof(derived) will be the sum of the elements in the class.

Also, I want the individual elements for the class be named x, y, z and w, so to give an array like access with the [] operator I previously simply returned the addess of x since it always was the first element to be stored. Sure, this assumes the compiler stores the elements consecutively, but isn't that guaranteed to happen?

Share this post


Link to post
Share on other sites
Quote:
Original post by Trenki
About the unsafe cast: I'm sure it would give the expected behavior for my case as all the derived classes will have their elements laid out one after the other by defining the member variables one after the other. There will be no virtual functions involved and therefore no vtable pointer generated and thus the sizeof(derived) will be the sum of the elements in the class.


Ah, yes, the typical "this is how I think the compiler works, so I guess it might do what I want it to" argument. The sad thing is, if you use inheritance, then your object is a non-PODS, and if it's a non-PODS, the "sizeof() is equal to the sum of members plus padding" guarantee vanishes (as well as any reinterpret-casting guarantee). The compiler will be allowed (and will probably do, for optimization purposes) to toy with the contents of your classes and you will get spurious untraceable results on 25% of your customer's computers.

This is, of course, ignoring the fact that you're using inheritance with a base class that doesn't have a virtual destructor (which in itself probably won't hurt you here, but is still a code smell).

Quote:
Also, I want the individual elements for the class be named x, y, z and w, so to give an array like access with the [] operator I previously simply returned the addess of x since it always was the first element to be stored. Sure, this assumes the compiler stores the elements consecutively, but isn't that guaranteed to happen?


Depends on the platform, access restrictions, and padding options. But yes, if your members are the only members, and if they're all within the same access modifier, and if the encompassing object is a PODS, and if you eliminate the possibility of padding, then they will be stored consecutively in memory. Otherwise, their position is not consecutive, either in a defined or undefined way.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
This is, of course, ignoring the fact that you're using inheritance with a base class that doesn't have a virtual destructor (which in itself probably won't hurt you here, but is still a code smell).


A virtual destructor is only ever required if you also have virtual functions! So there is no code smell.

Also, for my specific case the derived classes (actually just structs with member functions) will contain a specific number of elements all of the same type all with the same visibility, so there should not be any padding issues at all. Also I am sure that deriving from an empty base class which just provides default implementations of some member functions will not introduce any problems and also won't make the size of my derived classes larger.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!