Welcome





Sunday, March 27, 2011

Concept of method overloading in generic units:

In the Barton-Nackman technique, a template class derives from an instantiation of another template, passing itself as a template parameter to that other template as follows:

class A : public base<A> { ... };

The important application of this technique is to give a common “base template” for a set of template classes. One can overload operators and other non-member functions for the common base template; the derived classes will match the definitions. Because it avoids run-time dispatching, this technique is often used in the numerical domain [11–13, 42], The usage of the Barton-Nackman trick is best explainedwith an example,which we take from the domain of linear algebra. Matrix types (dense matrix, diagonal matrix, and triangular matrix) represent different kinds of matrices. Each of these matrix types has a common interface which can be exploited to define functions and operators that work for any matrix type. In this example, our interface is just one function.We overload the function call operator that gives the syntax A(i,j) for accessing the element at row I and column j of matrix A. This interface is defined in the common base template matrix:

template <class Derived>

class matrix {

public:

double operator()(int i, int j) const {

return static cast<const Derived&>(this)(i,j);

}

};

The matrix template will always be instantiated in such a way that the template parameter Derived is the type of a derived class. Note the static cast in the definition of operator(). As long as Derived is the actual type of the object, this is a safe downcast to a derived class. The function call operator thus invokes the function call operator of the derived class without dynamic dispatching. Each specialized matrix type must pass itself as a template argument to the matrix template and define the actual implementation of operator():

class dense matrix : public matrix<dense matrix> {

public:

double operator()(int i, int j) { ... }

...

};

class diagonal matrix : public matrix<diagonal matrix> {

public:

double operator()(int i, int j) { ... }

...

};

class triangular matrix : public matrix<triangular matrix> {

public:

double operator()(int i, int j) { ... }

...

};

With this arrangement, one can overload generic functions for the matrix template, instead of separately for each matrix type. For example, the multiplication operator could be defined as:

template<class A, class B>

typename product traits<A, B>::type

operator(const matrix<A>& a, const matrix<B>& b);

The product traits template is a traits class to determine the result type of multiplying matrices of types A and B. Such traits classes for deducing return types of operations are common in C++ template libraries [15, 40, 42].We can observe many benefits in this approach. The run-time overhead of virtual function calls is avoided. Several implementation types can be grouped under a common base template, allowing one operator implementation to cover many separate types.We can also identify several problems with the approach: One needs to be careful not to slice objects. Taking the matrix arguments by copy in the above operatorfunction would not copy the whole object, but only the base class matrix. Thus some data would be lost, leading to undefined behavior at runtime when the object was cast to the actual matrix type in the function call operator of the matrix class . It is difficult to get the desired overloading behavior if some parameters of a function are instances of “Barton-Nackman powered” types, but others are not. For example, one might want to define multiplication between two matrices, and additionally between a matrix and a scalar with the scalar as either the left or right argument. The straightforward solution is to define three function templates (we leave the return types unspecified):

template<class A, class B>

... operator(const matrix<A>& a, const B&b);

template<class A, class B>

... operator(const A& a, const matrix<B>& b);

template<class A, class B>

... operator(const matrix<A>&m1, const matrix<B>&m2);

The following code demonstrates why this approach does not work:

diagonal matrix A; dense matrix B;

A B; // error, ambiguous call

The third function is not the best match; insteadthe call is ambiguous. The first two definitions are both better than the third definition, but neither is better than the other. Thus, a compiler error occurs. Even though the typematrix<A> is more specialized than A, the actual argument type is not matrix<A> but diagonal matrix, which derives from matrix<A> where A is diagonal matrix. Therefore, matching diagonal matrix with matrix<A> requires a cast, whereasmatching it with A does not, making the latter a better match. Thus, the second definition provides a better match for the first argument, and the first definition provides a better match for the second argument; this makes the call ambiguous. There are two immediate workarounds: providing explicit overloads for all common scalar types, or overloading for the element types of the matrix arguments. The first solution is tedious and not generic because one cannot know all the possible (user-defined) types that might be used as scalars. The second solution is limiting, as it prevents sensible combinations of argument types, such as multiplying a matrix with elements of type double with a scalar of type int.

The curiously recurring template pattern is quite a mind-teaser; it significantly adds to the complexity of the code. Type classes and the enable if template solve the above problems. We rewrite our matrix example using type classes. First, all matrix types must be instances of the following type class:

template <class T, class Enable = void>

struct matrix traits {function that uses assignment, and to provide the specialized implementations as overloads.

Without enable if and type class emulation, overloading is limited.for example, it is not possible to define a swap function for all types that derive from a particular class; the generic swap is a better match if the overloaded function requires a conversion from a derived class to a base class (see section 4). Type classes allow us to express the exact overloading rules. First, two type classes are defined. Assignable represents types with assignment and copy construction (as in the Standard Library), and User Swappable is for types that overload the generic swap function:

template <class T, class Enable = void>

struct assignable traits { static const bool conforms = false; };

template <class T>

struct assignable {

BOOST STATIC ASSERT((assignable traits<T>::conforms));

};

template <class T, class Enable = void>

struct user swappable traits { static const bool conforms = false; };

template <class T>

struct user swappable {

BOOST STATIC ASSERT(user swappable traits<T>::conforms);

static void swap(T& a, T& b) {

user swappable traits<T>::swap(a,b);

}

};

The Assignable requirements are assignment and copy construction, which are not subject to overload resolution, and thus need not be routed via the type class mechanism. Second, two overloaded definitions of generic swap are provided. The first is for types that are instances of User Swappable. The function forwards calls to the swap function defined in the User Swappable type class:

template <class T>

typename enable if<user swappable traits<T>::conforms, void>::type

generic swap(T& a, T& b) {

user swappable<T>::swap(a,b); }

};

The second overload is used for types which are instances of Assignable but not instances of User Swappable. The exclusion is needed to direct types that are both Assignable and User Swappable to the customized swap:

template <class T>

typename enable if<

assignable traits<T>::conforms && !user swappable traits<T>::conforms,

void>::type

generic swap(T& a, T& b) {

T temp(a); a = b; b = temp;

}

static const bool conforms = false;

};

template <class T>

struct matrix {

BOOST STATIC ASSERT(matrix traits<T>::conforms);

static double index(const T& M, int i, int j) {

return matrix traits<T>::index(M, i, j);

}

};

Thus, the generic swap function can be defined in the most efficient way possible for each type. There is no fear of overload resolution accidentally picking a function other than that the programmer intended.

No comments:

Post a Comment