Contents
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:
It's easy to crash the VCC 7.1 compiler by using a cast operator that doesn't exist:
template < class _Ty > class Cast
{
public:
void crash( ClassX* );
};
template < class _Ty >
void Cast< _Ty >::crash( ClassX* x )
{
x->operator _Ty(); }
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
.
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:
template < typename _Ty > class tplInfo
{
public:
virtual const char* info( void ) const;
};
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:
template < typename _Ty > class tplInfo
{
public:
const char* info( void ) const;
};
template < typename _Ty > class tplInfoEx : public tplInfo< _Ty >
{
public:
};
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 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:
#pragma once
#include <functional>
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 );
}
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):
#include "tplS.h"
template < >
const 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.
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:
template < typename _Ty > class tplT
{
public:
typedef _Ty value_type;
public:
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*()
{
return NULL;
}
There are two typedef
s 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:
class Iterator : public std::iterator<
std::forward_iterator_tag, _Ty >
{
typename Iterator::value_type& operator*();
};
Have you ever tried the very useful static functions of std::numeric_limits
?
#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:
#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.
#define NOMINMAX
#include <windows.h>
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:
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;
}
};
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; };
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:
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; };
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.