Template pseudo-specialization through compile-time assertions

R

Ruben Campos

Greetings.

Some time ago, I had writing a CVector <T, N> class, which implements an
algebraic vector of arbitrary both dimension and scalar type. First, I
defined the interface for the generic algebraic vector class. The
problem I encountered there was that algebraic vectors of some concrete
dimensions were susceptible to include some extra methods not included
in the most generic case. For example, 3-dimensional vectors include the
cross product, an operation not defined over vectors of lesser dimension
(and also greater... but my mathematic knowledge is not so vast); also,
I'd like to include for 2, 3 and 4-dimensional vectors, methods to
access the cartesian coordinates by name (x, y, z and w).

The two alternatives I found (after seeking and asking here for advice)
to deal with this required to write partial specializations of CVector
<T, N> class, a) rewriting all common behaviour or b) inheriting from a
CBaseVector <T, N> class which include that common behaviour.

Recently, I've reviewed CVector <T, N> source code, and I've found
another alternative (which I think is simpler and clearer than the
previous ones). Now, I have only one CVector <T, N> class generic
definition, without partial (or total) specializations, and that class
definition includes all methods expected to be both common or restricted
to certain dimension (N) values. Then, the implementation of those
restricted methods includes compile time assertions to ensure that they
are not used over vectors with wrong dimension values.

I include here a sample of (incomplete, but I hope that enough to
illustrate the explined) source code. First, the class interface:

template <typename T, std::size_t N>
class CVector
{
public:

// Common behaviour
CVector (T const & initval = T());
CVector (CVector <T,N> const & other);

T & operator[] (std::size_t const index);
T const & operator[] (std::size_t const index) const;

CVector <T,N> & operator= (CVector <T,N> const & rhs);

CVector <T,N> & operator+= (CVector <T,N> const & rhs);
CVector <T,N> & operator-= (CVector <T,N> const & rhs);
CVector <T,N> & operator*= (T const & rhs);
CVector <T,N> & operator/= (T const & rhs);

// ...

// Restricted behaviour
CVector (T const & x, T const & y);
// Only for 2-dimensional vectors
CVector (T const & x, T const & y, T const & z);
// Only for 3-dimensional vectors
CVector (T const & x, T const & y, T const & z, T const & w);
// Only for 4-dimensional vectors

void set (T const & x, T const & y);
// Only for 2-dimensional vectors
void set (T const & x, T const & y, T const & z);
// Only for 3-dimensional vectors
void set (T const & x, T const & y, T const & z, T const & w);
// Only for 4-dimensional vectors

T & x ();
// Only for 1- to 4-dimensional vectors
T const & x () const;
// Only for 1- to 4-dimensional vectors

T & y ();
// Only for 2- to 4-dimensional vectors
T const & y () const;
// Only for 2- to 4-dimensional vectors

T & z ();
// Only for 3- to 4-dimensional vectors
T const & z () const;
// Only for 3- to 4-dimensional vectors

T & w ();
// Only for 4-dimensional vectors
T const & w () const;
// Only for 4-dimensional vectors

CVector <T,N> & operator^= (CVector <T,N> const & rhs);
// Cross product, only for 3-dimensional vectors

// ...

private:

// ...
};

And then, a samples of a restricted method implementation:

template <typename T, std::size_t N>
T &
CVector <T,N>::y ()
{
STATIC_ASSERT((N >= 2) && (N <= 4));

return (*this)[1];
}

STATIC_ASSERT could be any implementation of a compile time assertion
mechanism. For now, I'm using the second variant described in the book
"Modern C++ design: generic programming and design patterns applied", by
Andrei Alexandrescu.

I've successfully tested the described approach. Also, I've found it
clearer than the two previous ones which imply partial specializations,
and it doesn't duplicate code nor it requires auxiliar classes.

But I still want to ask here about the goodness of this approach. Is it
correct, from a formal point of view? Does it have any weak spot I've
overlooked? Is there any way of further improving the design?

Thank you in advance for your time and your advice.
 
?

=?iso-8859-1?B?Sm9hcXXtbiBNIEzzcGV6IE118W96?=

Ruben said:
Greetings. [...]
And then, a samples of a restricted method implementation:

template <typename T, std::size_t N>
T &
CVector <T,N>::y ()
{
STATIC_ASSERT((N >= 2) && (N <= 4));

return (*this)[1];
}

Hi Rubén,

I think you can do this in a cleaner way using
boost::enable_if:

http://boost.org/libs/utility/enable_if.html

Hope this helps,

Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo
 
R

Ruben Campos

Joaquín M López Muñoz said:
Ruben said:
Greetings.
[...]

And then, a samples of a restricted method implementation:

template <typename T, std::size_t N>
T &
CVector <T,N>::y ()
{
STATIC_ASSERT((N >= 2) && (N <= 4));

return (*this)[1];
}


Hi Rubén,

I think you can do this in a cleaner way using
boost::enable_if:

http://boost.org/libs/utility/enable_if.html

Hope this helps,

Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo

First of all, thank you for your diligent answer.

I've immediately read the boost::enable_if documentation, I've test it
(not in depth, for obvious time reasons) and my conclussion is as follows.

The only way boost::enable_if represents a change from the STATIC_ASSERT
implementation (in the case I described in my first mail) is by moving
the restriction check from method's implementation to method's
declaration. I think this carries two disadvantages: a) first, it makes
the class declaration a bit harder to read; and second, b)
boost::enable_if must be typed twice, both in function's declaration and
in function's implementation (I'm currently placing each of them into
separate header and source files, also for template classes), and so
must be done with the restriction.

The only advantage I see is that boost::enable_if provides information
about the restriction directly in the class declaration itself, saving a
user to be addressed to the implementation (which should be kept
hidden). But the same can be made through comments and/or class
documentation (in fact, I've currently included the restriction
description into the comments attached to the function's implementation,
from which Doxygen constructs the class documentation).

So I'm not able to see the way in which boost::enable_if can help in my
case. Please, correct me if I am wrong, or explain me advantages of
boost::enable_if that I've not seen.

Thank you for your time.
 
?

=?iso-8859-1?B?Sm9hcXXtbiBNIEzzcGV6IE118W96?=

Ruben Campos ha escrito:
The only way boost::enable_if represents a change from the STATIC_ASSERT
implementation (in the case I described in my first mail) is by moving
the restriction check from method's implementation to method's
declaration. I think this carries two disadvantages: a) first, it makes
the class declaration a bit harder to read; and second, b)
boost::enable_if must be typed twice, both in function's declaration and
in function's implementation (I'm currently placing each of them into
separate header and source files, also for template classes), and so
must be done with the restriction.

The only advantage I see is that boost::enable_if provides information
about the restriction directly in the class declaration itself, saving a
user to be addressed to the implementation (which should be kept
hidden). But the same can be made through comments and/or class
documentation (in fact, I've currently included the restriction
description into the comments attached to the function's implementation,
from which Doxygen constructs the class documentation).

So I'm not able to see the way in which boost::enable_if can help in my
case. Please, correct me if I am wrong, or explain me advantages of
boost::enable_if that I've not seen.

Hello again,

Well, I've thought twice and now I realize that boost::enable_if
is not applicable to your case, as it can only be used with
(member) function templates.

In the case of (member) function templates, the advantage
of boost::enable_if is that the template, if disabled
for a particuar type T, does not even enter into the
associated overload set: with the static assert technique,
such a template could be selected by the overload resolution
rules only to trigger a compile time fail. But then again,
your case is different as your restricted member functions
are not templates. So, sorry for providing you a misguided
hint.

Best,

Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top