4

In C++98 I got used to using call_traits in my templated functions to automatically pick the best way to pass parameters, e.g.:

template<class T>
void foo(typename boost::call_traits<T>::param_type arg)
{
    // .. do stuff with arg ..
}

The advantage being that for primitives it would pass by value and for more complex objects it would pass by reference, so I'd have the least amount of overhead possible. C++11 now has a concept of 'universal references':

template<class T>
void foo(T&& arg)
{
    // .. do stuff with arg ..
}

As I understand it I need to use a universal reference in order to get perfect forwarding with std::forward, so if I plan to use that the choice is clear. But when I don't plan to, which should I prefer? Will a universal reference always be as good or better?

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • 3
    The two are just different. For example, in the first version the template argument cannot be deduced. – Kerrek SB Oct 22 '12 at 17:06
  • @KerrekSB: Perhaps I went too far in simplifying my example. Typically foo will be non-templated but will be inside a templated class. I don't mind if the type can't be deduced though -- my question is whether the universal reference will generate as efficient code. – Joseph Garvin Oct 22 '12 at 18:22
  • 1
    As I said, they're different things. Use the boost thing if you *know* the type you want. Use a universal reference if you want to allow *any* type. – Kerrek SB Oct 22 '12 at 18:32
  • @KerrekSB: The point of the boost thing is using it when you don't know what type you want. For some Ts (primitives) you want to by value passing and for some Ts you want by reference passing. I'm having trouble compromising what you're saying with what I was told in a previous question (that I should prefer universal reference -- I'm trying to understand if there's a perf penalty to that): http://stackoverflow.com/questions/12548614/should-templated-functions-take-lambda-arguments-by-value-or-by-rvalue-reference – Joseph Garvin Oct 22 '12 at 18:55
  • 7
    I find `call_traits` to be pretty useless with universal references, tbh. The call traits were an effort to argument passing optimization, which isn't needed anymore in C++11. Just take a universal reference and perfect-forward into whatever you want. Really, all `call_traits` did was give you a value type for built-ins and a reference type for everything else. – Xeo Oct 22 '12 at 19:04
  • @Xeo: But that was a useful optimization. You're saying that a universal reference will give the same result? That's basically my question. – Joseph Garvin Oct 22 '12 at 19:22
  • To be honest, I wouldn't really call it an optimization after all. It's just for generic code to be able to do the same thing non-generic code does. And thinking about it, the only time a built-in argument might be faster to pass by-value rather than by-reference would be if `sizeof(T) < sizeof(void*)` (`char`, `bool`, `short`). Perfect-forwarding enables a much more important optimization - *move semantics*. If you use `call_traits`, you'll never get that for all parameters without N^2 (`const&`, `&&`) or N^3 (+`&`) overloads and to solve that is what perfect forwarding was invented for – Xeo Oct 22 '12 at 19:41
  • @Xeo: I think that should be sizeof(T) <= sizeof(void*). When the sizes are equal the value can still be passed in a register. which means a struct with a 2 bools and 3 shorts in it could fit on 64-bit machines. Generally when you're working with objects that small move optimization isn't an optimization anyway (there's no depth to the object so it's always a copy). Also you're saying I can't use std::enable_if to make a call traits that does copying when sizeof(T) <= sizeof(void*) and otherwise uses a universal reference? – Joseph Garvin Oct 22 '12 at 20:23
  • 2
    @Xeo: Not just N^2, but 2^N! And no, that's not factorial. – GManNickG Oct 22 '12 at 21:22
  • @GMan: Right, too bad I can't edit anymore. :) Joseph: You either get your call traits *or* template argument deduction. Choose one, I personally prefer the deduction by far. – Xeo Oct 22 '12 at 21:24
  • 1
    @JosephGarvin: My point was that in your first example, `T` is fixed (never mind the referenciness). With the universal template, you *don't* fix the type at all, but allow the user to pass *anything* and just hope that this will be useful later on. It's essentially deferring any potential conversions to a later stage. – Kerrek SB Oct 22 '12 at 21:42
  • 1
    So, in a nutshell, the Boost code can be used (inside a class template) to write a *function* in a generic fashion, while the universal references are used when writing function *templates*. I still think that those are distinct use cases, although it may be feasible to replace one by the other in certain (common) situations. – Kerrek SB Oct 22 '12 at 21:43

0 Answers0