Click here to Skip to main content
15,896,912 members
Articles / Programming Languages / C++
Article

Special notes for writing C++ templates

Rate me:
Please Sign up or sign in to vote.
4.59/5 (15 votes)
31 Jan 20064 min read 67.7K   221   43   1
Six hints for using templates.

Contents

Introduction

The following article contains six topics about C++ features, and curious compiler behaviour that wasted a lot of my time during my last project. Derive benefit from these experiences if you want. The first subject is:

The crashing cast operator

It's easy to crash the VCC 7.1 compiler by using a cast operator that doesn't exist:

C++
template < class _Ty > class Cast
{
public:
  void crash( ClassX* );
};

template < class _Ty >
void Cast< _Ty >::crash( ClassX* x )
{
  //    there is no operator _Ty() in ClassX
  x->operator _Ty();    //    fatal error C1001:
}

This is particularly annoying because you won't find out what's wrong with your code. The ICC 8.1 (Intel) simply tells you: {class "`ClassX"' has no member "`operator char"'. This is much more better. Interestingly, there is no such problem with VCC 7.1 when using ClassX as a reference parameter: ClassX& x.

Template inheritance and specialization

The documentation says that the expression templateName< p > could be used anywhere, like classes. So you can derive a new template. This is a big deal. But what about specialization? VCC is a little bit careless here. Look at the following code:

C++
template < typename _Ty > class tplInfo
{
public:
  virtual const char* info( void ) const/* = 0*/;
};


template < typename _Ty > class tplInfoEx : public tplInfo< _Ty >
{
public:
    virtual const char* info( void ) const;
};

template < >
const char* tplInfo< int >::info( void ) const;

template < >
const char* tplInfoEx< int >::info( void ) const;

The definition of the specialization is dropped here. The code works fine, and all compilers are satisfied. But now I will remove something:

C++
template < typename _Ty > class tplInfo
{
public:
  /*virtual*/ const char* info( void ) const/* = 0*/;
};


template < typename _Ty > class tplInfoEx : public tplInfo< _Ty >
{
public:
  //virtual const char* info( void ) const;
};

//template < >
//const char* tplInfo< int >::info( void ) const;

template < >
const char* tplInfoEx< int >::info( void ) const;

Now, we have a specialization for a method that doesn't exist. The declaration of tplInfoEx< int >::info( void ) was commented out. But VCC gives us neither an error nor a warning. All compiles fine and runs. ICC works more precisely. We get the error message: declaration is incompatible. In my opinion, that's right.

Static members

Static class members have to be defined outside the class definition. But templates are pure declarations, there is no implementation file.

We have the following options:

  • One generic initialization in the .h, .hpp file.
  • Multiple specialized initializations in a .cpp file.
  • Both of the above. A generic and multiple specialized initialization could be mixed trouble-free.
  • Abandon a static member in templates. Use a base class with the static values instead.

Here is an example:

C++
#pragma once
#include <functional>

//    +-------------------------------------------------------+
//    | tplS [declaration]                                    
//    +-------------------------------------------------------+
template < typename _Ty > class tplS
{
public:
  typedef std::equal_to< _Ty >    key_compare;

public:
  bool foo( const _Ty&, const _Ty& );

private:
  const static key_compare    _comparator;
};

template < typename _Ty >
bool tplS< _Ty >::foo( const _Ty& left, const _Ty& right )
{
  return _comparator.operator ()( left, right );
}

// STATIC _comparator OBJECT
template< typename _Ty >
  const typename tplS< _Ty >::key_compare
    tplS< _Ty >::_comparator = tplS< _Ty >::key_compare();

The code above could be supplemented with the following code in an implementation file (tplS.cpp):

C++
#include "tplS.h"

template < >
  const /*typename*/ tplS< int >::key_compare
    tplS< int >::_comparator = tplS< int >::key_compare();

Annotation: missing initializations of static members will be reported by the linker, not the compiler.

Overlapping typedefs

It must be clearly distinguished between typedefs that are inherited and typedefs from the enclosing namespace. If there is a collision, the enclosing namespace rules. Look at the following example:

C++
template < typename _Ty > class tplT
{
public:
  typedef _Ty value_type;

public:
  //    std::iterator contains: typedef _Ty value_type;
  class Iterator : public std::iterator< 
                   std::forward_iterator_tag, _Ty >
  {
    value_type& operator*();
  };
};

template< class _Ty >
typename tplT< _Ty >::Iterator::value_type& 
                  tplT< _Ty >::Iterator::operator*()
{
  //    illustrative material only
  return NULL;
}

There are two typedefs with the name value_type of the same type. So, VCC 7.1 compiles all fine. But ICC 8.1 grumbles: declaration is incompatible with tplT<_Ty>::value_type &tplT<_Ty>::Iterator::operator*(). And it is right. If the definition forces the usage of the typedef specified by the iterator, this applies to the declaration too. The following modification solves the problem:

C++
class Iterator : public std::iterator< 
                 std::forward_iterator_tag, _Ty >
{
    //value_type& operator*();    //    wrong!
    typename Iterator::value_type& operator*();
};

Define to the max

Have you ever tried the very useful static functions of std::numeric_limits?

C++
#include <limits>

std::cout
  << std::max< int >( 2, 3 )
  << ", "
  << std::numeric_limits< int >::max()
  << std::endl;

The code above compiles except if windows.h was included. There are macros like:

C++
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif

which invalidate the STL templates. Everyone knows that macros like min and max are very fragile. That's why we should prevent the definition of such macros and use the templates instead.

C++
#define NOMINMAX
#include <windows.h>

Code bloat

Code bloat is the horror. After an imprudent code change, the size of your binary is exploding. There are several techniques to avoid this, like empty base optimization (EBO). I have found that you have to particularly identify code that does not depend on your template parameters to stuck them in a base class or anywhere else, plus usage of static functions wherever possible. A full analysis would go beyond the scope of this article. But a small example follows:

C++
template 
< typename _Ty, 
  unsigned _Count
> class Impl : public Data< _Ty, _Count >
{
public:
  Impl( _Ty* value )
    : Data< _Ty, _Count >( value )
  {}
  int operator++( int )
  {
    return _refCount++;
  }
  void print() const
  {
    std::cout
      << _refCount
      << std::endl;
  }
};

//    <-- snip -->

template 
< 
  typename _Ty,
  unsigned _Count
> class tplB : public Impl< _Ty, _Count >
{
  typedef tplB< _Ty, _Count - 1 >    _Bloat;

public:
  explicit tplB( _Ty* );
  void foo() const;
  _Ty* getValue() const;

private:
  _Bloat*    _bloat;    //    bloating
};

The only sense of the recursive template definition is to achieve a big count of different classes. This is perfect to demonstrate code bloat but makes no sense in any other way. Most of the functions are completely independent from the template parameter _Ty or could be cast in a safe way:

C++
struct Impl
{
  Impl( void* );
  int operator++( int );
  void print() const;

  template < typename _Ty >
  _Ty* convert() const
  {
    return reinterpret_cast <_Ty*> ( _value );
  }

  int        _refCount;
  void*    _value;
};

template 
< 
  typename _Ty,
  unsigned _Count
> class tplC : public Impl
{
  typedef tplC< _Ty, _Count - 1 >    _Bloat;

public:
  explicit  tplC( _Ty* );
  void foo() const;
  _Ty* getValue() const;

private:
  _Bloat*    _bloat;    //    bloating
};

//    <-- snip -->

This is really bad design, but clarifies the principle and saves tons of bytes in complex situations. The template itself doesn't contain any data members. This reduces the template code size dramatically. Look at the full source code to get an overview. Compile and link it without optimization, to realize the different sizes of the binaries.

Annotation: usage of virtual destructors is strongly advised in derived classes but becomes redundant in typesafe templates. One virtual class method - and a destructor is one - needs up to eight bytes. That's why you should use virtual functions sparingly.

Conclusion

As a C++ coder, you have no choice. You have to read up the language spec. And in cases of doubt, ask a second compiler.

History

  • 2006-Feb-01:
    • Original article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
osy
Software Developer (Senior)
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGreat comparisons on compiler glitches Pin
Shawn Poulson1-Feb-06 1:30
Shawn Poulson1-Feb-06 1:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.