C++ gurus have been exhorting us to pass parameters into functions via const reference since time immemorial.   For a large-sized type, this makes perfect sense:  You're pushing less stuff onto the stack, saving copying time (as well as constructor calls).

But if you think about it long enough, you'll realize you shouldn't do this all of the time.   Many types of objects take up as much space as (or less space than) any reference. char, many int types, enumerations, float, small-enough classes, and most importantly, pointers.  When you pass objects of these types into a function by const reference, you pay the penalty of extra indirections to get to the object values.  So, smaller objects are ideally passed in by value.

In a traditional function this doesn't matter so much.  You know which type you are using, and you can tailor parameters to suit the type being used. But in a template you lose some flexibility: there's only one main template declaration, and you can only declare it one way.   This problem appears all over the C++ Standard Library (descended from the old Standard Template Library), which uses const references with reckess abandon (incidentally screwing up the C++ function binders).

Of course, you could specialize the template for all of the smaller types, but there are a LOT of them, and your code becomes much harder to maintain that way.

But don't lose hope. There's a way to get your compiler to automatically determine whether a type should be passed in by value, or by const reference. The following C++ template class illustrates how to do it:

template <class T>
class type_traits
{
 template<bool TOO_BIG>
 struct decide
 {
  typedef T const &parameter;
 };
 template<>
 struct decide<false>
 {
  typedef T parameter;
 };
 static const bool TOO_BIG=sizeof(T)>sizeof(void *);

 public:

 typedef typename decide<TOO_BIG>::parameter parameter;
};


The template uses the compile-time constant function sizeof to pick one specialization of the decide template to tell us what our parameter type should be. Since most C++ compilers implement references as pointers with an attitude,  sizeof(void *) is probably the best choice for a cutoff size, but you might also want to use sizeof(T*).

So, if we have

template<class T> void foo(typename type_traits<T>::parameter arg);

then the specialization foo<int> will give us foo(int), and foo<double>will give us foo (double const &)or foo (double), depending on the compiler's implementation of double.   This last illustrates an added benefit of using this technique: When your code is ported to another platform, the parameter types will be automatically adjusted based upon their sizes.

In a class template, of course, you'll want a typedef to make things easier on the eyes:

template<class T, size_t N>
class tuple
{
 T t [N];
 public:
 typedef typename type_traits<T>::parameter parameter;
 typedef T *iterator;
 typedef T *const_iterator;
 tuple(parameter it=T());
 tuple (tuple const &i);
 tuple fill (parameter ft);
 T &operator[](size_t i) { return t[i]; }
 parameter operator[](size_t i) const { return t[i]; }
};

The technique breaks down for small classes that have complicated constructors.

template <class T>
class deep_ptr
{
 T *p;
 public:
 typedef T value_type;
 typedef typename type_traits<T>::parameter value_parameter;
 typedef T &value_reference;

 deep_ptr(T *ip=0) { p=new T(*ip); }
 deep_ptr (deep_ptr<T> const &i) { p=new T(*i.p); }
 ~deep_ptr() { delete p; }
 value_parameter operator *() const { return *p; }
 value_reference operator *() { return *p; }
 T const * const operator->() const { return p; }
 T * const operator->() { return p; }
};

Notice that the technique is perfectly all right for the template argument T, but if you made a tuple<deep_ptr<T>, N> things would be slower than they needed to be.

You may have a couple of problems compiling the above code if your C++ compiler isn't sophisticated enough:

  • A lot of compilers still don't allow for member templates, or have problems with explicit specialization of member templates.  Some of those, however, allow for partial specialization of templates, and you can move the decide template outside of the class:

    template<class T, bool TOO_BIG>
    struct decide
    {
     typedef T const &parameter;
    };
    
    template<class T>
    struct decide<T, false>
    {
     typedef T parameter;
    };
  • Some compilers (notably Visual C++) have a problem with defining static const members in a class and using them as constant expressions at compile time.  For these compilers, you will have to define TOO_BIG in an enum (the venerable "enum hack"):

    enum too_big_ { TOO_BIG=sizeof(T)>sizeof(void *) };

    and you may need to static_cast TOO_BIG to get it to go into the template:

    typedef typename decide<static_cast<bool>(TOO_BIG)>::parameter parameter;

Log in or register to write something here or to contact authors.