tags:

views:

197

answers:

5

If I have a little peice o' code as such...

template <typename _T>
class Foo
{
public:
    typedef const T& ParamType;
    void DoStuff(ParamType thingy);
};

This can be non-optimal if sizeof(_T) <= sizeof(_T*).

Therefore, I want to have a conditional typedef. If the size of _T is less than or equal to that of a pointer, just pass it in by value. Otherwise, pass it by const reference. Is this possible? I hear all this stuff about templates being turing complete but this is hurting my head.

+4  A: 

You can do that, but it's a little tricky.

My main concern here if your motive. I understand that you don't want to pass large objects by value, but whether or not the object is large is irrelevant -- what you really want to know is how long the copy constructor takes to create the parameter.

As a concrete example, the size of an std::vector is actually very tiny as it allocates all the memory on the heap and only needs a pointer. However, copying a vector takes much more time. Something like that is not something you can really include in a condition.

You're best bet here is to just pass by const & so that you cover the worst case. Also, I can't guarantee this, but the I believe that a compiler would be smart enough to pass by value if it thought that was more efficient.

Peter Alexander
In this case, it's for a fast NxM matrix class. However, on x86 platforms it's a bit easier to pass a double by const reference than by value. If I'm wrong, please call me on it.
Clark Gaebel
@wowus - I'm assuming you've measured and have some profiling statistics showing that passing small types by value instead of const reference provides a significant speedup in your application?
Chris Lutz
I don't know whether you're right or wrong, and I'm sure it will depend on the particular CPU. What I do know is that your compiler does know, and will choose the best option for you.
Peter Alexander
Haha, if by profile dumps you mean staring at assembly dumps, then yes.
Clark Gaebel
Yep. Pass by reference makes the aliasing issue a little harder for the compiler, but for an inline function, passing a byte by reference is pretty much the same as passing by value. If you empirically find that by-value is faster than by-reference in a particular case, try making a local copy of the object in the caller's scope as that can help it with aliasing+`inline` issues (while the copy operation itself is just imaginary).
Potatoswatter
@wowus, you are normally saving a dereferencing when passing by value (even if a double takes two words on 32-bit architectures) which in the long run could defer a potential cache miss. And Chris is right, it is paramount that you profile (i.e. time) your code with both by-value and by-reference semantics, not just look at the assembly -- you may be surprised. :)
vladr
It's a good point that the size of the object alone does not give you any indication how expensive it is to copy. E.g, sizeof GCC's string is 4, which by the OP's simplistic rule would fall under the "good for copying" category.
UncleBens
+2  A: 

You can use Boost call traits.

template <typename _T>
class Foo
{
public:
    void DoStuff(boost::call_traits<_T>::param_type thingy);
};

From the documentation:

If T is a small built in type or a pointer, then param_type is defined as T const, instead of T const&.

R Samuel Klatchko
That doesn't do what he wants to do. It does avoid some other problems though, so I guess it's kind of relevant.
Peter Alexander
Can you provide a concrete example?
Clark Gaebel
Hmm, do you know how it's implemented? I just want to grab the relevant parts instead of pulling in all of boost.
Clark Gaebel
@Poita_ - other then the exact rule of when it's a value or a reference, it's exactly what he's looking for.
R Samuel Klatchko
@wowus - it's done via template forwarding and specialization. For boost code, it's actually pretty easy to follow: http://www.boost.org/doc/libs/1_42_0/boost/detail/call_traits.hpp
R Samuel Klatchko
+2  A: 

Quite easy to achieve using partial template specialization.

template< typename _T, bool _ByVal >
struct FooBase {
  typedef const _T& ParamType;
};

template< typename _T >
struct FooBase< _T, true > {
  typedef const _T ParamType;
};

template< typename _T, bool _ByVal = sizeof(_T) <= sizeof(void*) >
class Foo : public FooBase< _T, _ByVal > {
  typedef typename FooBase< _T, _ByVal >::ParamType ParamType;
  void DoStuff(ParamType thingy);
};

EDIT As per Jeff's sol'n one should indeed compare sizeof(_T) and sizeof(_T&) but I kept the original <= void* requirement.

vladr
Gave you the accepted, and jeff the upvote :)Both were the same idea, and exactly what I wanted. Thank you.
Clark Gaebel
You can skip one of the specializations by putting the typedef in the general template.
Peter Alexander
Quite correct in this case. Also updated to allow an explicit _ByVal override at the Foo level.
vladr
+3  A: 
template <class T, bool Smaller>
class BestArgumentPassingType {};

template <class T>
class BestArgumentPassingType<T, true> {
  public:
    typedef T Type;
};

template <class T>
class BestArgumentPassingType<T, false> {
  public:
    typedef const T& Type;
};

template <class T>
class ArgumentType : public BestArgumentPassingType<T, sizeof(T) < sizeof(T*)> {
};

struct B { double d; double d2; };
struct S { double d; };

class A {
  public:
    static void foo(ArgumentType<B>::Type big);
    static void bar(ArgumentType<S>::Type small);
};

int main()
{
  B b;
  S s;
  A::foo(b);
  A::bar(s);
  return 0;
}

Like so.

Jeff Walden
Johannes Schaub - litb
Jeff Walden
Johannes Schaub - litb
Jeff Walden
@Jeff, um that's disgusting behavior of gdb. I'm afraid.
Johannes Schaub - litb
+2  A: 

I know it's not directly what you asked for, but it's your goal. I would use a different way to achieve this: Pass classes (this includes structs and unions) by reference, and pass everything else by value.

template<typename>
struct tovoid { typedef void type; };

template<typename T, typename = void>
struct parm_type {
  typedef T type;
};

template<typename T>
struct parm_type<T, typename tovoid<int T::*>::type> {
  typedef T const& type;
};

template <typename T>
class Foo
{
public:
    typedef typename parm_type<T>::type ParamType;
    void DoStuff(ParamType thingy);
};

This implements the (in my opinion) very good point that @Poita_ made.

Johannes Schaub - litb