Sign in to follow this  

C++ template template parameters

This topic is 3743 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)[i] += rhs[i]; return *this; }
	C& operator -= (const C& rhs) { for (int i = 0; i < count; ++i) (*this)[i] -= rhs[i]; return *this; }
	C& operator *= (const C& rhs) { for (int i = 0; i < count; ++i) (*this)[i] *= rhs[i]; return *this; }
	C& operator /= (const C& rhs) { for (int i = 0; i < count; ++i) (*this)[i] /= rhs[i]; return *this; }
	
	C& operator += (const T& rhs) { for (int i = 0; i < count; ++i) (*this)[i] += rhs; return *this; }
	C& operator -= (const T& rhs) { for (int i = 0; i < count; ++i) (*this)[i] -= rhs; return *this; }
	C& operator *= (const T& rhs) { for (int i = 0; i < count; ++i) (*this)[i] *= rhs; return *this; }
	C& operator /= (const T& rhs) { for (int i = 0; i < count; ++i) (*this)[i] /= 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
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[i];
}
T & operator[] (unsigned int i) {
assert(i < D);
return data[i];
}
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[i]);
}
T & operator[] (unsigned int i) {
assert(i < 2);
return *(this->*data[i]);
}
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)[i] -= rhs[i];
return static_cast<Derived&>(*this);
}

Derived& operator += (const value_type& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this)[i] += 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)[i] -= rhs[i];
return static_cast<Derived&>(*this);
}

Derived& operator += (const T& rhs)
{
for (int i = 0; i < count; ++i)
static_cast<Derived&>(*this)[i] += 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
Quote:
Original post by Trenki
A virtual destructor is only ever required if you also have virtual functions! So there is no code smell.


  1. A code smell is something which isn't always incorrect, but is often incorrect. Inheriting from a class without a virtual destructor is often incorrect, though I did mention that it probably wasn't in your case.
  2. A virtual destructor has nothing to do with the absence or existence of other virtual functions. It is mandatory as soon as you will delete your derived through a base pointer.


Quote:
Also, for my specific case the derived classes 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.


Actually, padding issues are a completely orthogonal concept (they're enabled or disabled through compiler directives) to ordering of fields (which is undefined if you're not in a POD.

Quote:
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.


Until you find specific mention of what you're doing in the C++ Standard (unlikely) or in your compiler's documentation (probable) stating that you can do it, your certainty is, at best, hope.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Actually, padding issues are a completely orthogonal concept (they're enabled or disabled through compiler directives) to ordering of fields (which is undefined if you're not in a POD.


Can you please tell me the section in the C++ standard where it says that reordering is allowed or that the order of members is undefined. I would like to read it.

Also, can you tell me a compiler which makes your class bigger by simply deriving from an empty base class which does not have any virtual functions?

Share this post


Link to post
Share on other sites
Quote:
Original post by Trenki
Can you please tell me the section in the C++ standard where it says that reordering is allowed or that the order of members is undefined. I would like to read it.


Quote:
C++ Standard, 9.2 Class members, §12

Nonstatic data members of a (non-union) class declared without an
intervening access-specifier are allocated so that later members have
higher addresses within a class object. The order of allocation of
nonstatic data members separated by an access-specifier is unspecified
(_class.access.spec_).
Implementation alignment requirements might
cause two adjacent members not to be allocated immediately after each
other; so might requirements for space for managing virtual functions
(_class.virtual_) and virtual base classes (_class.mi_).

C++ Standard, 10 Derived classes, §3

The order in which the base class subobjects are allocated in the most
derived object (_intro.object_) is unspecified.




(Emphasis mine)

Quote:
Also, can you tell me a compiler which makes your class bigger by simply deriving from an empty base class which does not have any virtual functions?


I have no idea. 9.2 §12 above says they can, for padding purposes, but this doesn't mean they either will or won't do it. I'm not saying that you're not right in making non-standard assumptions, I'm just saying that verifying these assumptions would require too much work (reading the documentation of every compiler out there) and be too unstable (you'd have to check again every time a new compiler version appears) to be of any practical use outside of one-shot prototype code.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Why not provide an inline subscript operator instead of returning an array?


I want to pass the vetors and matrices to OpenGL functions and those take pointers to an array.

Share this post


Link to post
Share on other sites
There's a trick (I call it the SNK trick since snk_kid showed it to me) to do everything you want portably.

Warning: Copy-Pasting my code will not teach you anything

First, the template class itself (vector.hpp):


//=====================================================================
//
// vector
// ---------
// This is a templated 3D vector class. It's designed to be
// specialised if required, but that's not necessary (say, if you
// have need for a 72 dimension vector). It takes the two template
// arguments of E and T, where E is the number of elements, and T
// is the type of those elements.
//
// As it stands, g++ can't handle having the definitions of the
// methods in the class itself, so we only declare them in the class
// and define them later on. Further down the file, we have a series
// of helpful vector functions. These do not need to be specialised
// for specialised versions of vector.
//
//=====================================================================
#ifndef ATMA_MATH_VECTOR_HPP
#define ATMA_MATH_VECTOR_HPP
//=====================================================================
#include <cmath>
//=====================================================================
#include "atma_math__core.h"
#include "vector2.hpp"
#include "vector3.hpp"
#include "vector4.hpp"
//=====================================================================
ATMA_MATH_BEGIN
//=====================================================================

//=====================================================================
// Vector of E dimensions of type T
//=====================================================================
template <size_t E, typename T = float> struct vector
{
//=====================================================================
// Constructor: Default
//=====================================================================
vector()
: m_elements()
{
}

//=====================================================================
// Access
//=====================================================================
inline T& operator ()(size_t i)
{
return m_elements[i];
}

inline const T& operator ()(size_t i) const
{
return m_elements[i];
}

//=====================================================================
// Self-Addition
//=====================================================================
inline vector<E, T>& operator += (const vector<E, T>& rhs)
{
for (size_t i = 0; i < E; ++i) (*this)(i) += rhs(i);
return *this;
}

//=====================================================================
// Self-Subration
//=====================================================================
inline vector<E, T>& operator -= (const vector<E, T>& rhs)
{
for (size_t i = 0; i < E; ++i) (*this)(i) -= rhs(i);
return *this;
}

//=====================================================================
// Self-Multiplication
//=====================================================================
template <typename Y> vector<E, T>& operator *= (const Y& rhs)
{
for (size_t i = 0; i < E; ++i) (*this)(i) *= rhs;
return *this;
}

//=====================================================================
// Self-Division
//=====================================================================
template <typename Y> vector<E, T>& operator /= (const Y& rhs)
{
for (size_t i = 0; i < E; ++i) (*this)(i) /= rhs;
return *this;
}

protected:
// the elements in our vector
T m_elements[E];
};



//=====================================================================
//
// S T A N D A R D O P E R A T O R S
// ----------------------------------------
//
// These operators are pretty standard for vectors, so we allow them
// to be only written once (here), and used for a vector of any size.
//
//=====================================================================

//=====================================================================
// Addition: Vector
//=====================================================================
template <size_t E, typename T>
inline const vector<E, T> operator + (const vector<E, T>& lhs, const vector<E, T>& rhs)
{
vector<E, T> v = lhs;
v += rhs;
return v;
}

//=====================================================================
// Subraction: Vector
//=====================================================================
template <size_t E, typename T>
inline const vector<E, T> operator - (const vector<E, T>& lhs, const vector<E, T>& rhs)
{
vector<E, T> v = lhs;
v -= rhs;
return v;
}

//=====================================================================
// Multiplication: Scalar
//=====================================================================
template <size_t E, typename T, typename Y>
const vector<E, T> operator * (const vector<E, T>& lhs, const Y& rhs)
{
vector<E, T> v = lhs;
v *= rhs;
return v;
}

template <size_t E, typename T, typename Y>
const vector<E, T> operator * (const Y& lhs, const vector<E, T>& rhs)
{
vector<E, T> v = rhs;
v *= lhs;
return v;
}

//=====================================================================
// Division: Scalar
//=====================================================================
template <size_t E, typename T, typename Y>
const vector<E, T> operator / (const vector<E, T>& lhs, const Y& rhs)
{
vector<E, T> v = lhs;
v /= rhs;
return v;
}

template <size_t E, typename T, typename Y>
const vector<E, T> operator / (const Y& lhs, const vector<E, T>& rhs)
{
vector<E, T> v = rhs;
v /= lhs;
return v;
}

//=====================================================================
// Equality
//=====================================================================
template <size_t E, typename T>
inline bool operator == (const vector<E, T>& lhs, const vector<E, T>& rhs)
{
for (size_t i = 0; i < E; ++i)
{
if (lhs(i) != rhs(i)) return false;
}
return true;
}

//=====================================================================
// Inequality
//=====================================================================
template <size_t E, typename T>
inline bool operator != (const vector<E, T>& lhs, const vector<E, T>& rhs)
{
for (size_t i = 0; i < E; ++i)
{
if (lhs(i) != rhs(i)) return true;
}
return false;
}




//=====================================================================
//
// H E L P F U L F U N C T I O N S
// ---------------------------------------
//
// These functions here operate on any vector and are the usual
// suspects for 3D graphical workings. They rely on the implementation
// used for the vectors.
//
//=====================================================================

//=====================================================================
// Dot-product
//=====================================================================
template <size_t E, typename T>
float DotProduct(const vector<E, T>& v1, const vector<E, T>& v2)
{
float result = 0;
for (size_t i = 0; i < E; ++i) result += float(v1(i) * v2(i));
return result;
}

//=====================================================================
// Normalisation
//=====================================================================
template <size_t E, typename T>
vector<E, T> Normalize(const vector<E, T>& v)
{
float m = Magnitude(v);
if (m == 1) return v;
return v / m;
}

//=====================================================================
// Magnitude
//=====================================================================
template <size_t E, typename T>
float Magnitude(const vector<E, T>& v1)
{
float f = 0;
for (size_t i = 0; i < E; ++i) f += v1(i) * v1(i);
return std::sqrt(f);
}

//=====================================================================
// Returns the angle between two vectors
//=====================================================================
template <size_t E, typename T>
float Angle(const vector<E, T>& lhs, const vector<E, T>& rhs)
{
float m = Magnitude(lhs) * Magnitude(rhs);
if (m == 0.0f) return 0.0f;
return acos(DotProduct(lhs, rhs) / m);
}

//=====================================================================
// Returns the midpoint between two vectors
//=====================================================================
template <size_t E, typename T>
vector<E, T> Midpoint(const vector<E, T>& v1, const vector<E, T>& v2)
{
return (v1 + v2) * 0.5f;
}




//=====================================================================
ATMA_MATH_CLOSE
//=====================================================================
#endif
//=====================================================================







And one (1) specialisation, for a 2D vector class (which you can access with .x and .y). Yes, it's possible to make it return an array, but I'll leave that as an exercise for the reader (vector2.hpp):


//=====================================================================
//
// vector<2, T>
// ---------------
// Specialisation for two dimensional vertices. Allows us to give
// them the .x, .y properties to make it useful to us.
//
// This was rewritten on 20060629 to get rid of the "anonymous union
// and struct" trick (which is non-standard), and make use of "SNK's
// trick" (snk_kid from www.gamedev.net fame). It's merely a
// standard C++ way of achieving the same thing.
//
//=====================================================================
// We are a specialisation, so we want the user to include vector.hpp,
// not vectorX.hpp - so if they do, we do it the proper way for them.
//=====================================================================
// if vector.hpp wasn't included first
#ifndef ATMA_MATH_VECTOR_HPP
// throw an error
#error vector2.hpp should not be directly included. Include vector.hpp.
//=====================================================================
#else
//=====================================================================
#ifndef ATMA_MATH_VECTOR2_HPP
#define ATMA_MATH_VECTOR2_HPP
//=====================================================================
ATMA_MATH_BEGIN
//=====================================================================
template <size_t E, typename T> struct vector;
//=====================================================================

template <typename T> struct vector<2, T>
{
//=====================================================================
// Constructor
//=====================================================================
vector(T x = 0, T y = 0) : x(x), y(y)
{
}

//=====================================================================
// Access
//=====================================================================
inline T& operator ()(size_t i)
{
return this->*mSNK[i];
}

inline const T& operator ()(size_t i) const
{
return this->*mSNK[i];
}

//=====================================================================
// Self-Addition
//=====================================================================
inline vector<2, T>& operator += (const vector<2, T>& rhs)
{
for (size_t i = 0; i < 2; ++i) (*this)(i) += rhs(i);
return *this;
}

//=====================================================================
// Self-Subration
//=====================================================================
inline vector<2, T>& operator -= (const vector<2, T>& rhs)
{
for (size_t i = 0; i < 2; ++i) (*this)(i) -= rhs(i);
return *this;
}

//=====================================================================
// Self-Multiplication
//=====================================================================
template <typename Y> vector<2, T>& operator *= (const Y& rhs)
{
for (size_t i = 0; i < 2; ++i) (*this)(i) *= rhs;
return *this;
}

//=====================================================================
// Self-Division
//=====================================================================
template <typename Y> vector<2, T>& operator /= (const Y& rhs)
{
for (size_t i = 0; i < 2; ++i) (*this)(i) /= rhs;
return *this;
}

private:
// SNK's trick, with the variable named in his honour
typedef T vector<2, T>::* const vec[2];
static const vec mSNK;

public:
T x, y;
};

//=====================================================================
// static member initialisation
//=====================================================================
template<typename T>
const typename vector<2, T>::vec vector<2, T>::mSNK
= { &vector<2, T>::x, &vector<2, T>::y };

//=====================================================================
ATMA_MATH_CLOSE
//=====================================================================
#endif
//=====================================================================
#endif
//=====================================================================



Share this post


Link to post
Share on other sites

This topic is 3743 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this