banner



How To Inherit From Template Class

Extensible Templates: Via Inheritance or Traits?

Excerpted from the book More Infrequent C++ by Herb Sutter. Copyright � 2002 Addison-Wesley. Printed with permission of the publisher. This article appeared in C/C++ Users Journal, 20(two), Feb 2002.

The Greek philosopher Socrates taught past asking his students questions�questions designed to guide them and assist them draw conclusions from what they already knew, and to show them how the things they were learning related to each other and to their existing knowledge. This method has become so famous that nosotros now call information technology the �Socratic method.� From our point of view as students, Socrates� arroyo involves united states of america, makes us think, and helps united states relate and apply what we already know to new information. More Infrequent C++ [1] uses a Socratic problem-solution format to teach how to make effective use of standard C++ and its standard library with a particular focus on sound software engineering in modernistic C++.

In this excerpt from More than Infrequent C++ (Item four, fatigued from the opening section on Generic Programming and the C++ Standard Library), our focus is on traits templates and some cool traits techniques. Even without traits, what tin a template effigy out about its type�and what tin it do near it? And how, and then, can traits make life even clearer and more than extensible? The answers are nifty and illuminating, and not just for people who write C++ libraries.

Trouble

ane. What is a traits grade?

2. Demonstrate how to discover and brand utilise of template parameters� members, using the following motivating instance: You lot desire to write a class template C that tin can only be instantiated on types that have a const fellow member office named Clone() that takes no parameters and returns a pointer to the same kind of object.

// T must provide
// T* T::Clone() const
template<typename T>
class C
{
  // ...
};

Annotation: It�due south obvious that if C writes code that only tries to invoke T::Clone() without parameters, then such code will neglect to compile if there isn�t a T::Clone() that can be chosen without parameters. Merely that�due south not enough to respond this question, considering just trying to call T::Clone() without parameters would besides succeed in calling a Clone() that has default parameters and/or does not render a T*. The goal hither is to specifically enforce that T provide a role that looks exactly like this: T* T::Clone() const.

three. A developer wants to write a template that can require (or just discover) whether the blazon on which it is instantiated has a Clone() member function. The programmer decides to practise this past requiring that classes offer such a Clone() must derive from a predetermined Cloneable base course. Demonstrate how to write the following template:

template<typename T>
grade X
{
  // ...
};

a) to require that T be derived from Cloneable; and

b) to provide an alternative implementation if T is derived from Cloneable, and piece of work in some default fashion otherwise.

4. Is the approach in #3 the best way to require/detect the availability of a Clone()? Depict alternatives.

5. How useful is it for a template to know that its parameter blazon T is derived from some other type? Does knowing nearly such derivation give any benefit that couldn�t besides be accomplished without the inheritance relationship?

Solution

1. What is a traits form?

Quoting 17.1.18 in the C++ standard [2], a traits class is:

�a class that encapsulates a set of types and functions necessary for template classes and template functions to manipulate objects of types for which they are instantiated.� (1)

The idea is that traits classes are instances of templates, and are used to carry extra information�especially information that other templates can apply�almost the types on which the traits template is instantiated. The nice thing is that the traits grade T<C> lets us tape such actress data about a class C, without requiring any alter at all to C.

For instance, in the standard itself std::char_traits<T> gives information most the character-like blazon T, specially how to compare and manipulate such T objects. This information is used in such templates equally std::basic_string and std::basic_ostream to let them to work with character types that are not necessarily char or wchar_t, including working with user-defined types for which yous provide a suitable specialization of std::char_traits. Similarly, std::iterator_traits provides data about iterators that other templates, especially algorithms and containers, can put to adept use. Even std::numeric_limits gets into the traits act, providing information near the capabilities and behavior of various kinds of numeric types as they�re implemented on your particular platform and compiler.

For more examples, come across:

bullet

Items 30 and 31 on smart pointer members

bullet

Exceptional C++ [3] Items two and three, which prove how to customize std::char_traits to customize the beliefs of std::basic_string

bullet

The April, May, and June 2000 issues of C++ Report, which contained several fantabulous columns near traits

 

Requiring Fellow member Functions

2. Demonstrate how to detect and make use of template parameters� members, using the following motivating case: Yous want to write a class template C that tin only be instantiated on types that have a const member office named Clone() that takes no parameters and returns a pointer to the same kind of object.

// Example ii
//
// T must provide
// T* T::Clone() const
template<typename T>
course C
{
  // ...
};

Note: It�due south obvious that if C writes code that only tries to invoke T::Clone() without parameters, and then such code will fail to compile if at that place isn�t a T::Clone() that can be called without parameters.

For an instance to illustrate that last note, consider the following:

// Case two(a): Initial attempt,
// sort of requires Clone()
//
// T must provide /*...*/ T::Clone( /*...*/ )
template<typename T>
class C
{
public:
  void SomeFunc( const T* t )
  {
    // ...
    t->Clone();
    // ...
  }
};

The commencement problem with Example 2(a) is that information technology doesn�t necessarily require anything at all. In a template, only the fellow member functions that are actually used will exist instantiated. (two) If SomeFunc() is never used, information technology will never be instantiated, and then C tin can easily be instantiated with types T that don�t have anything resembling Clone().

The solution is to put the lawmaking that enforces the requirement into a office that�southward sure to be instantiated. Many people put information technology in the constructor, because it�s impossible to utilise C without invoking its constructor somewhere, right? (This approach is mentioned, for example, in [four].) True enough, but there could exist multiple constructors. Then, to be rubber, we�d accept to put the requirement-enforcing code into every constructor. An easier solution is to put it in the destructor. After all, at that place�s only one destructor, and it�due south unlikely that C will be used without invoking its destructor (by creating a C object dynamically and never deleting it). So, perhaps, the destructor is a somewhat simpler place for the requirement-enforcing lawmaking to alive:

// Example ii(b): Revised attempt, requires Clone()
//
// T must provide /*...*/ T::Clone( /*...*/ )
template<typename T>
class C
{
public:
  ~C()
  {
    // ...
    const T t; // kind of wasteful, plus also requires
               // that T take a default constructor
    t.Clone();
    // ...
  }
};

That�south still not entirely satisfactory. Let�s set this aside for at present, just nosotros�ll soon come up back and improve this enforcement further, after we�ve done a fleck more than thinking nearly it.

This leaves us with the second problem: Both Examples 2(a) and ii(b) don�t then much examination the constraint as simply rely on information technology. (In the case of Example two(b), it�s even worse considering 2(b) does it in a wasteful way that adds unnecessary runtime code just to endeavor to enforce a constraint.)

But that�south non enough to answer this question, because just trying to call T::Clone() without parameters would also succeed in calling a Clone() that has default parameters and/or does not return a T*.

The code in Examples 2(a) and 2(b) will indeed work most swimmingly if in that location is a function that looks similar T* T::Clone(). The trouble is that it will besides work most swimmingly if at that place is a part void T::Clone(), or T* T::Clone( int = 42 ), or with another oddball variant signature, similar T* T::Clone( const char* = �xyzzy� ), merely as long as it can be chosen without parameters. (For that thing, it will piece of work even if there isn�t a Clone() member role at all, every bit long as there�s a macro that changes the proper name Clone to something else, but there�due south piddling nosotros can do about that.)

All that may be fine in some applications, only information technology�s non what the question asked for. What we desire to achieve is stronger:

The goal here is to specifically enforce that T provide a function that looks exactly like this: T* T::Clone() const.

So hither�s i way nosotros can do it:

// Example 4-ii(c): Better, requires
// exactly T* T::Clone() const
//
// T must provide T* T::Clone() const
template<typename T>
class C
{
public:
  // in C�due south destructor (easier than putting it
  // in every C constructor):
  ~C()
  {
    T* (T::*test)() const = &T::Clone;
    test; // suppress warnings about unused variables
          // this unused variable is likely to be optimized
          // away entirely
    // ...
  }

  // ...
};

Or, a little more than cleanly and extensibly:

// Example iv-2(d): Culling way of requiring
// exactly T* T::Clone() const
//
// T must provide T* T::Clone() const
template<typename T>
class C
{
  bool ValidateRequirements() const
  {
    T* (T::*test)() const = &T::Clone;
    examination; // suppress warnings about unused variables
    // ...
    return truthful;
  }

public:
  // in C�due south destructor (easier than putting it
  // in every C constructor):
  ~C()
  {
    assert( ValidateRequirements() );
  }

  // ...
};

Having a ValidateRequirements() role is extensible, for it gives us a dainty clean place to add any time to come requirements checks. Calling it inside an assert() farther ensures that all traces of the requirements machinery volition disappear from release builds.

Constraints Classes

There�s an even cleaner way to do it, though. The following technique is publicized past Bjarne Stroustrup in his C++ Mode and Technique FAQ [five], crediting Alex Stepanov and Jeremy Siek for the use of pointer to part.

Suppose we write the following HasClone constraints class:

// Example iv-2(east): Using constraint inheritance
// to crave exactly T* T::Clone() const
//

// HasClone requires that T must provide
// T* T::Clone() const
template<typename T>
class HasClone
{
public:
  static void Constraints()
  {
    T* (T::*test)() const = &T::Clone;
    test; // suppress warnings most unused variables
  }
  HasClone() { void (*p)() = Constraints; }
};

Now we have an elegant�dare I say �cool�?�mode to enforce the constraint at compile time:

template<typename T>
form C : HasClone<T>
{
  // ...
};

The idea is uncomplicated: Every C constructor must invoke the HasClone<T> default constructor, which does zilch but exam the constraint. If the constraint exam fails, most compilers will emit a fairly readable error message. The HasClone<T> derivation amounts to an assertion about a characteristic of T in a way that�south like shooting fish in a barrel to diagnose.

Requiring Inheritance, Accept ane: IsDerivedFrom1 Value Helper

3. A programmer wants to write a template that tin require (or only find) whether the type on which it is instantiated has a Clone() member function. The programmer decides to do this by requiring that classes offer such a Clone() must derive from a predetermined Cloneable base class. Demonstrate how to write the following template:

template<typename T>
grade Ten
{
  // ...
};

a) to crave that T be derived from Cloneable

We�ll take a await at two approaches. Both work. The outset approach is a bit tricky and complex; the 2nd is simple and elegant. It�s valuable to consider both approaches because they both demonstrate interesting techniques that are good to know about, even though one technique happens to exist more applicable here.

The beginning approach is based on Andrei Alexandrescu�south ideas in �Mappings Between Types and Values� [6]. Get-go, we ascertain a helper template that tests whether a candidate type D is derived from B. Information technology determines this by determining whether a pointer to D can be converted to a pointer to B. Here�s one style to practise it, like to Alexandrescu�southward approach:

// Example four-3(a): An IsDerivedFrom1 value helper
//
// Advantages: Can be used for compile-time value exam
// Drawbacks: Pretty complex
//
template<typename D, typename B>
class IsDerivedFrom1
{
  grade No { };
  form Yes { No no[3]; };

  static Yeah Test( B* ); // alleged, but not defined
  static No Test( ... ); // declared, but not defined

public:
  enum { Is = sizeof(Examination(static_cast<D*>(0))) == sizeof(Yep) };
};

Get information technology? Remember almost this code for a moment before reading on.

* * * * *

The higher up trick relies on three things:

one. Yes and No have different sizes. This is guaranteed past having a Yeah contain an array of more than i No object. (And anyway, two negatives sometimes do make a positive. This time it�s not really a No no.)

ii. Overload resolution and determining the value of sizeof are both performed at compile time, not runtime.

3. Enum values are evaluated, and can exist used, at compile time.

Permit�s analyze the enum definition in more particular. Showtime, the innermost function is:

Test(static_cast<D*>(0))

All this does is mention a office named Test and pretend to pass information technology a D*�in this case, a suitably cast null pointer will do. Note that nothing is actually being done hither, and no lawmaking is being generated, and then the pointer is never dereferenced or, for that matter, even ever actually created. All we�re doing is creating a typed expression. At present, the compiler knows what D is, and will apply overload resolution at compile time to decide which of the two overloads of Examination() ought to be called: If a D* can be converted to a B*, so Test( B* ), which returns a Yes, would get selected; otherwise, Test( ... ), which returns a No, would get selected.

The obvious next stride, and so, is to cheque which overload would get selected:

sizeof( Examination(static_cast<D*>(0)) ) == sizeof(Yes)

This expression, withal evaluated entirely at compile time, volition yield 1 if a D* can be converted to a B*, and 0 otherwise. That�south pretty much all we want to know, because a D* can exist converted to a B* if and merely if D is derived from B, or D is the same as B. (3)

So at present that nosotros�ve calculated what we demand to know, we just need to store the outcome someplace. The said �someplace� has to exist a identify that tin can be set and the value used, all at compile time. Fortunately, an enum fits the beak nicely:

enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) };

In this case, for our Cloneable purposes, we don�t intendance if D and B are the same type. Nosotros just desire to know whether a D tin exist used polymorphically equally a B, and that�s what�s being tested in IsDerivedFrom1. It�s trivially true that a B can exist used every bit a B.

That�s information technology. Nosotros can now use this facility to help build an answer to the question, to wit:

// Example 4-3(a), continued: Using IsDerivedFrom1
// helper to enforce derivation from Cloneable
//
template<typename T>
grade 10
{
  bool ValidateRequirements() const
  {
    // typedef needed considering otherwise the , will be
    // interpreted as delimiting macro parameters to assert
    typedef IsDerivedFrom1<T, Cloneable> Y;

    // a runtime check, but 1 that can exist turned
    // into a compile-fourth dimension cheque without much piece of work
    affirm( Y::Is );

    return true;
  }

public:
  // in X�s destructor (easier than putting it
  // in every Ten constructor):
  ~X()
  {
    affirm( ValidateRequirements() );
  }

  // ...
};

Requiring Inheritance, Take two: IsDerivedFrom2 Constraints Base Class

By now you�ve probably noticed that we could use Stroustrup�due south arroyo to create a functionally equivalent version, which is syntactically nicer:

// Example iv-3(b): An IsDerivedFrom2 constraints base class
//
// Advantages: Compile-time evaluation
//             Simpler to employ direct
// Drawbacks:  Not directly usable for compile-time value exam
//
template<typename D, typename B>
class IsDerivedFrom2
{
  static void Constraints(D* p)
  {
    B* pb = p;
    pb = p; // suppress warnings virtually unused variables
  }

protected:
  IsDerivedFrom2() { void(*p)(D*) = Constraints; }
};

// Force it to fail in the case where B is void
template<typename D>
class IsDerivedFrom2<D, void>
{
  IsDerivedFrom2() { char* p = (int*)0; /* mistake */ }
};

Now the check is much simpler:

// Case 4-3(b), connected: Using IsDerivedFrom2
// constraints base to enforce derivation from Cloneable
//
template<typename T>
class X : IsDerivedFrom2<T,Cloneable>
{
  // ...
};

Requiring Inheritance, Take 3: A Merged IsDerivedFrom

The chief reward of IsDerivedFrom1 over IsDerivedFrom2 is that IsDerivedFrom1 provides an enum value that�southward generated, and tin can be tested, at compile time. This isn�t of import to the class X case shown here, but it volition exist of import in the following section, when nosotros desire to switch on just such a value to select different traits implementations at compile fourth dimension. On the other manus, IsDerivedFrom2 provides pregnant ease of use for the mutual case, when we just need to place a requirement on a template parameter to ensure some facility will exist, merely without doing anything fancy, such every bit selecting from among alternative implementations. We could just provide both versions, only the duplicated and similar functionality is a problem, especially for naming. We can�t do much meliorate to distinguish them than we have washed, namely by tacking on some wart to make the names unlike, and so that users would always have to recollect whether they wanted IsDerivedFrom1 or IsDerivedFrom2. That�s ugly.

Why not have our cake and eat information technology, likewise? Let�south merely merge the ii approaches:

// Example four-3(c): An IsDerivedFrom constraints base of operations
// with testable value
//
template<typename D, typename B>
class IsDerivedFrom
{
  form No { };
  course Yes { No no[3]; };

  static Yes Test( B* ); // not defined
  static No Test( ... ); // not divers

  static void Constraints(D* p) { B* pb = p; pb = p; }

public:
  enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) };

  IsDerivedFrom() { void(*p)(D*) = Constraints; }
};

Selecting Alternative Implementations

The solutions in iii(a) are overnice and all, and they�ll make certain T must be a Cloneable. Simply what if T isn�t a Cloneable? What if there were some culling action we could have? Perhaps we could make things fifty-fifty more flexible�which brings us to the second office of the question.

b) [...] provide an culling implementation if T is derived from Cloneable, and work in some default mode otherwise.

To do this, we introduce the proverbial �extra level of indirection� that solves many computing problems. In this case, the extra level of indirection takes the class of a helper template: X will utilise IsDerivedFrom from Example 4-3(c), and utilise partial specialization of the helper to switch between �is-Cloneable� and �isn�t-Cloneable� implementations. (Note that this requires the compile-fourth dimension testable value from IsDerivedFrom1, also incorporated into IsDerivedFrom, and so that nosotros accept something nosotros can test in society to switch among unlike implementations.)

// Example four-3(d): Using IsDerivedFrom to make use of
// derivation from Cloneable if available, and exercise
// something else otherwise.
//
template<typename T, int>
course XImpl
{
  // general case: T is not derived from Cloneable
};

template<typename T>
grade XImpl<T, 1>
{
  // T is derived from Cloneable
};

template<typename T>
class X
{
  XImpl<T, IsDerivedFrom<T, Cloneable>::Is> impl_;
  // ... delegates to impl_ ...
};

Do y'all run into how this works? Let�south work through it with a quick instance:

class MyCloneable : public Cloneable { /*...*/ };

X<MyCloneable> x1;

X<T>�s impl_ has type:

XImpl<T, IsDerivedFrom<T, Cloneable>::Is>

In this case, T is MyCloneable, then X<MyCloneable>�southward impl_ has type:

XImpl<MyCloneable, IsDerivedFrom<MyCloneable, Cloneable>::Is>

which evaluates to

XImpl<MyCloneable, one>

which uses the specialization of XImpl that makes use of the fact that MyCloneable is derived from Cloneable. Merely what if we instantiate 10 with some other type? Consider:

X<int> x2;

Now T is int, and and so X<int>�s impl_ has blazon

XImpl<MyCloneable, IsDerivedFrom<int, Cloneable>::Is>

which evaluates to

XImpl<MyCloneable, 0>

which uses the unspecialized XImpl. Nifty, isn�t information technology? It�s not even hard to use, once written. From the user�s point of view, the complication is hidden inside X. From the point of view of X�due south author, information technology�s just a matter of directly reusing the mechanism already encapsulated in IsDerivedFrom, without needing to understand how the magic works.

Note that we�re not proliferating template instantiations. Exactly 1 XImpl<T,...> volition ever be instantiated for any given T, either XImpl<T,0> or XImpl<T,ane>. Although XImpl�s 2d parameter could theoretically have any integer value, nosotros�ve prepare things upwards here and then that the integer can but always exist 0 or 1. (In that example, why not use a bool instead of an int? The respond is, for extensibility: Information technology doesn�t injure to use an int, and doing so allows additional alternative implementations to be added hands in the future every bit needed�for example, if nosotros later want to add support for some other bureaucracy that has a Cloneable-like base class with a different interface.)

Requirements Versus Traits

iv. Is the arroyo in #three the best way to require/find the availability of a Clone()? Draw alternatives.

The arroyo in Question #3 is nifty, merely I tend to similar traits better in many cases�they�re about as simple (except when they have to exist specialized for every form in a hierarchy), and they�re more than extensible as shown in Items 30 and 31.

The idea is to create a traits template whose sole purpose in life, in this example, is to implement a Clone() performance. The traits template looks a lot like XImpl, in that there�ll exist a general-purpose unspecialized version that does something general-purpose, likewise as possibly multiple specialized versions that deal with classes that provide better or just dissimilar ways of cloning.

// Instance 4-iv: Using traits instead of IsDerivedFrom
// to make use of Cloneability if available, and do
// something else otherwise. Requires writing a
// specialization for each Cloneable grade.
//
template<typename T>
class XTraits
{
public:
  // general example: use copy constructor
  static T* Clone( const T* p ) { return new T( *p ); }
};

template<>
form XTraits<MyCloneable>
{
public:
  // MyCloneable is derived from Cloneable, so employ Clone()
  static MyCloneable* Clone( const MyCloneable* p )
  {
    return p->Clone();
  }
};

// ... etc. for every class derived from Cloneable

Ten<T> then simply calls XTraits<T>::Clone() where appropriate, and it will do the right thing.

The main difference between traits and the plain old XImpl shown in Example 4-iii(b) is that, with traits, when the user defines some new type, the nigh work that has to be done to utilise it with X is external to X �just specialize the traits template to �do the correct affair� for the new type. That�s more extensible than the relatively hard-wired approach in #3 above, which does all the selection inside the implementation of XImpl instead of opening it up for extensibility. It also allows for other cloning methods, not merely a function specifically named Clone() that is inherited from a specifically named base of operations class, and this too provides extra flexibility.

For more details, including a longer sample implementation of traits for a very similar example, see More Exceptional C++ [1] Item 31, Examples 31-ii(d) and 31-ii(e).

Note: The main drawback of the traits arroyo above is that information technology requires individual specializations for every form in a hierarchy. At that place are ways to provide traits for a whole hierarchy of classes at a time, instead of tediously writing lots of specializations. Come across [7], which describes a groovy technique to practise just this. His technique requires minor surgery on the base form of the outside course hierarchy�in this case, Cloneable.

Inheritance Versus Traits

5. How useful is it for a template to know that its parameter type T is derived from some other blazon? Does knowing nearly such derivation give any benefit that couldn�t also exist achieved without the inheritance relationship?

There is lilliputian extra benefit a template tin can gain from knowing that one of its template parameters is derived from some given base class that it couldn�t gain more extensibly via traits. The only real drawback to using traits is that traits tin can require writing lots of specializations to handle many classes in a big hierarchy, but in that location are techniques that mitigate or eliminate this drawback.

A principal motivator for this Particular was to demonstrate that �using inheritance for categorization in templates� is perchance not as necessary a reason to apply inheritance every bit some have thought. Traits provide a more full general mechanism that�s more extensible when information technology comes time to instantiate an existing template on new types�such as types that come from a 3rd-party library�that may not be easy to derive from a foreordained base class.

References

[1] H. Sutter. More than Exceptional C++ (Addison-Wesley, 2002) , ISBN 0-201-70434-10.

[ii] ISO/IEC 14882:1998(E), Programming Languages�C++ (ISO and ANSI C++ standard).

[3] H. Sutter. Exceptional C++ (Addison-Wesley, 2000).

[4] B. Stroustrup. The Blueprint and Evolution of C++, section xv.4.ii (Addison-Wesley, 1994).

[5] B. Stroustrup. C++ Way and Technique FAQ. Available online via http://www.gotw.ca/publications/mxc++/bs_constraints.htm.

[6] A. Alexandrescu. �Traits on Steroids� (C++ Report, 12(half dozen), June 2000).

[vii] A. Alexandrescu. �Mappings Between Types and Values� (C/C++ Users Journal C++ Experts Forum, 18(10), October 2000). Available online via http://world wide web.gotw.ca/publications/mxc++/aa_mappings.htm.

Notes

(one) Here encapsulates is used in the sense of bringing together in one place, rather than hiding behind a shell. It�s common for everything in traits classes to be public, and indeed they are typically implemented equally struct templates.

(2) Eventually, all compilers will get this rule correct. Yours might nonetheless instantiate all functions, non just the ones that are used.

(3) Or B happens to be void.

How To Inherit From Template Class,

Source: http://www.gotw.ca/publications/mxc++-item-4.htm

Posted by: balesdeally70.blogspot.com

0 Response to "How To Inherit From Template Class"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel