Trait

To write a template function that would take only parameters of type int, float, double and not allow any other types to be used for specialization of a template:
#include <iostream>
using namespace std;

template <typename T>
struct TypeTrait {
  enum { typeid_ = -1 };
};

#define DEFINE_TRAIT(type, id)   \
  template <>                    \
  struct TypeTrait<type> {       \
    enum { typeid_ = id};        \
  };

DEFINE_TRAIT(int, 1);
DEFINE_TRAIT(float, 2);
DEFINE_TRAIT(double, 3);

// prevent compiler warning because variable unused
template <typename X>
void no_unused_warning(X x) {}

template <typename T>
void foo(const T& v)
{
  // Compile-time type filtering
  char Invalid_Argument_DataType_To_foo[TypeTrait<T>::typeid_];
  no_unused_warning(Invalid_Argument_DataType_To_foo);

  // Runtime type identification
  switch (TypeTrait<T>::typeid_)
  {
    case TypeTrait<int>::typeid_:
      cout << "Receive integer" << endl; break;
    case TypeTrait<float>::typeid_:
      cout << "Receive float" << endl; break;
    case TypeTrait<double>::typeid_:
      cout << "Receive double" << endl; break;
    default: cout << "Receive unknown type" << endl;
  }

  // ...
}

int main(int argc, char *argv[])
{
  foo(1);
  foo(1.0f);
  foo(1.0);
  foo(static_cast<short>(1));

  return 0;
}

Code calculates the average of an array:
template<class T>
T Average(const T* data, int numElements)
{
  T sum = 0;
  for (int i=0; i<numElements; i++)
    sum += data[i];
  return sum / numElements;
}
This will work properly if T is a floating-point type. But what if: What needed is a mapping (or compile-time function) which will produce double if T is an integral type, and just T otherwise. This can be done with traits.
template<class T>
struct FloatTrait {
  typedef T TFloat;
};
template<>
struct FloatTrait<int> {
  typedef double TFloat;
};
template<>
struct FloatTrait<char> {
  typedef double TFloat;
};

template<class T>
typename FloatTrait<T>::TFloat Average(const T* data, int numElements)
{
  typename FloatTrait<T>::TFloat sum = 0;
  for (int i=0; i<numElements; i++)
    sum += data[i];
  return sum / numElements;
}

The things you want to map from are template parameters of the class (or struct). The things you want to map to sit inside the class or struct. By using traits, it is avoided having to specialize this function template for the special cases.


Consider this operator, which adds two vector objects:
template<class T1, class T2>
Vector<?> operator+(Vector<T1>&, Vector<T2>&);
What should the return type be? Ideally, it would follow C-style type promotion rules, so that
Vector<int> + Vector<char> -> Vector<int>
Vector<int> + Vector<float> -> Vector<float>
// etc ...
plus maybe some extra rules:
Vector<float> + Vector<complex<float> > -> Vector<complex<float> > 
Type promotion (map from pairs of types to another type) is a perfect application for traits.
template<class T1, class T2>
struct PromoteTrait {
};

#define DECLARE_PROMOTE(A,B,C) \
  template<>                   \
  struct PromoteTrait<A,B> {   \
    typedef C TPromote;        \
  };

DECLARE_PROMOTE(int, char, int);
DECLARE_PROMOTE(int, float, float);
DECLARE_PROMOTE(float, complex<float>, complex<float>);
// etc ...

template<class T1, class T2>
Vector<typename PromoteTrait<T1,T2>::TPromote>
  operator+(Vector<T1>&, Vector<T2>&);

The down side of this approach is that have to write a program to generate all the specializations of promote trait -- and there are tonnes.

Conceptual overview of a bit dirty approach:
template<class T>
struct PrecisionTrait {
  enum { precisionRank = 0, knowPrecisionRank = 0 };
};

#define DECLARE_PRECISION(T,rank)                         \
  template<>                                              \
  struct PrecisionTrait<T> {                              \
    enum { precisionRank = rank, knowPrecisionRank = 1 }; \
  };

DECLARE_PRECISION(int, 100);
DECLARE_PRECISION(unsigned int, 200);
DECLARE_PRECISION(long, 300);
DECLARE_PRECISION(unsigned long, 400);
DECLARE_PRECISION(float, 500);
DECLARE_PRECISION(double, 600);
DECLARE_PRECISION(long double, 700);
DECLARE_PRECISION(complex<float>,800);
DECLARE_PRECISION(complex<double>,900);
DECLARE_PRECISION(complex<long double>,1000);

template<class T>
struct AutopromoteTrait {
  typedef T TNumtype;

};
#define DECLARE_AUTOPROMOTE(T1,T2) \
  template<>                       \
  struct AutopromoteTrait<T1> {    \
    typedef T2 TNumtype;           \
  };

// These are the odd cases where small integer types
// are automatically promoted to int or unsigned int for
// arithmetic.
DECLARE_AUTOPROMOTE(bool, int);
DECLARE_AUTOPROMOTE(char, int);
DECLARE_AUTOPROMOTE(unsigned char, int);
DECLARE_AUTOPROMOTE(short int, int);
DECLARE_AUTOPROMOTE(short unsigned int, unsigned int);

template<class T1, class T2, int promoteToT1>
struct Promote2 {
  typedef T1 TPromote;
};

template<class T1, class T2>
struct Promote2<T1,T2,0> {
  typedef T2 TPromote;
};

template<class T1Orig, class T2Orig>
struct PromoteTrait {
  // Handle promotion of small integers to int/unsigned int
  typedef typename AutopromoteTrait<T1Orig>::TNumtype T1;
  typedef typename AutopromoteTrait<T2Prig>::TNumtype T2;

  enum {
    // True if T1 is higher ranked
    T1IsBetter = PrecisionTrait<T1>::precisionRank
                 > PrecisionTrait<T2>::precisionRank,
    // True if we know ranks for both T1 and T2
    knowBothRanks = PrecisionTrait<T1>::knowPrecisionRank
                    && PrecisionTrait<T2>::knowPrecisionRank,
    // True if we know T1 but not T2
    knowT1butNotT2 = PrecisionTrait<T1>::knowPrecisionRank
                     && !(PrecisionTrait<T2>::knowPrecisionRank),
    // True if we know T2 but not T1
    knowT2butNotT1 = PrecisionTrait<T2>::knowPrecisionRank
                     && !(PrecisionTrait<T1>::knowPrecisionRank),
    // True if T1 is bigger than T2
    T1IsLarger = sizeof(T1) <= sizeof(T2),
    // know T1 but not T2: true
    // know T2 but not T1: false
    // Otherwise, if T1 is bigger than T2: true
    defaultPromotion = knowT1butNotT2 ? false : (knowT2butNotT1 ? true : T1IsLarger)
  };

  // If have both ranks, then use them.
  // If have only one rank, then use the unknown type.
  // If have neither rank, then promote to the larger type.
  enum {
    promoteToT1 = (knowBothRanks ? T1IsBetter : defaultPromotion) ? 1 : 0
  };

  typedef typename Promote2<T1,T2,promoteToT1>::TPromote TPromote;
};
Index