views:

187

answers:

4

As I understand it, when passing an object to a function that's larger than a register, it's preferable to pass it as a (const) reference, e.g.:

void foo(const std::string& bar)
{
    ...
}

This avoids having to perform a potentially expensive copy of the argument.

However, when passing a type that fits into a register, passing it as a (const) reference is at best redundant, and at worst slower:

void foo(const int& bar)
{
    ...
}

My problem is, I'd like to know how to get the best of both worlds when I'm using a templated class that needs to pass around either type:

template <typename T>
class Foo
{
  public:

    // Good for complex types, bad for small types
    void bar(const T& baz);   

    // Good for small types, but will needlessly copy comples types
    void bar2(T baz);             
};

Is there a template decision method that allows me to pick the correct type? Something that would let me do,

void bar(const_nocopy<T>::type baz);

that would pick the better method depending on the type?


Edit:

After a fair amount of timed tests, the difference between the two calling times is different, but very small. The solution is probably a dubious micro-optimization for my situation. Still, TMP is an interesting mental exercise.

+6  A: 

If variable copy time is significant, the compiler will likely inline that instance of a template anyway, and the const reference thing will be just as efficient.

Technically you already gave yourself an answer.

Just specialize the no_copy<T> template for all the nocopy types.

template <class T> struct no_copy { typedef const T& type; };

template <> struct no_copy<int> { typedef int type; };
EFraim
I suppose boost would have a is_pod<T> trait to avoid doing all those specializations?
sbi
is_pod is not really of help. You can have a really large POD (think of Win32 API structures), which you clearly want to pass by const reference.
EFraim
+1  A: 

The only solution I can think of is using a macro to generate a specialized template version for smaller classes.

Mark P Neyer
+1  A: 

First: Use const & - if the implementation is to large to be inlined, the cosnt & vs. argument doesn't make much of a difference anymore.

Second: This is the best I could come up with. Doesn't work correctly, because the compiler cannot deduce the argument type

template <typename T, bool UseRef> 
struct ArgTypeProvider {};

template <typename T>
struct ArgTypeProvider<T, true>
{
   typedef T const & ArgType;
};

template <typename T>
struct ArgTypeProvider<T, false>
{
   typedef T ArgType;
};

template <typename T>
struct ArgTypeProvider2 : public ArgTypeProvider<T, (sizeof(T)>sizeof(long)) >
{
};

// ----- example function
template <typename T>
void Foo(typename ArgTypeProvider2<T>::ArgType arg)
{
   cout << arg;
}

// ----- use
std::string s="fdsfsfsd";
// doesn't work :-(
// Foo(7);
// Foo(s);

// works :-)
Foo<int>(7);
Foo<std::string>(s);
peterchen
Both your first and second advice seconded by me. Just a nitpick: I'd do sizeof(T)>sizeof(long) or something along those lines in order to be platform-independent.
sbi
thanks sbi, good suggestion - I've updated the sample accordingly.
peterchen
+10  A: 

Use Boost.CallTraits:

#include <boost/call_traits.hpp>

template <typename T>
void most_efficient( boost::call_traits<T>::param_type t ) {
    // use 't'
}
Exactly what I was looking for.
luke