10

I can write a templated function this way

template<class T> void f(T x) {…}

or this way

template<class T> void f(T const& x) {…}

I guess that the second option can be more optimal as it explicitly avoids a copy, but I suspect that it can also fail for some specific types T (eg functors?). So, when should use the first option, and when to use the second? There are also this boost::call_traits<T>::param_type and boost::reference_wrapper that were in the answers to my previous question, but people don't use them everywhere, do they? Is there a rule of thumb for this? Thanks.

Enlico
  • 23,259
  • 6
  • 48
  • 102
Roman L
  • 3,006
  • 25
  • 37

5 Answers5

13

Is there a rule of thumb for this?

The same general rules for when to use pass by reference vs. pass by value apply.

If you expect T always to be a numeric type or a type that is very cheap to copy, then you can take the argument by value. If you are going to make a copy of the argument into a local variable in the function anyway, then you should take it by value to help the compiler elide copies that don't really need to be made.

Otherwise, take the argument by reference. In the case of types that are cheap to copy, it may be more expensive but for other types it will be faster. If you find this is a performance hotspot, you can overload the function for different types of arguments and do the right thing for each of them.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • So there is no template-specific issues at all? – Roman L Feb 02 '11 at 16:26
  • 2
    @7vies: Not really; the only "issue" is that instead of having just one type to think about, you have to decide what makes sense for the group of types with which the template will be used. – James McNellis Feb 02 '11 at 16:28
  • @James McNellis: I guess sometimes you cannot decide this, as for example STL would accept functors by value, while one could have a "heavily-stateful" functor and that would be inefficient. Should one use that `boost::call_traits::param_type` in this case, or stick to just pass-by-value as STL did? – Roman L Feb 02 '11 at 16:34
  • 2
    @7vies: `boost::call_traits` won't help you because `param_type` is a const reference for any user-defined type (and therefore all class types). The assumption in the STL is that function objects are cheap to copy; if you have an expensive-to-copy function object, the "right solution" is to make it less expensive to copy, if possible, or otherwise write a wrapper that makes it cheaper to copy. – James McNellis Feb 02 '11 at 16:38
  • @James McNellis: I assumed (though never tried) you can specialize that trait for your type if it should be passed by value. – peterchen Feb 03 '11 at 09:41
  • @peterchen: You could fully specialize it and that might be worthwhile. I've never used `call_traits`; it just seems like it's more trouble than it's worth (i.e., if it was really useful, why haven't the C++0x containers been updated to use similar techniques?). – James McNellis Feb 08 '11 at 03:41
  • It appears C++ 0x containers seem to rely on resoruce pilfering through rvalue references to bring down the cost of excessive copies. I wish any of these methods wouldn't have to rely on extensive libraries - or at least those libraries could generate specific compile time diagnostics. – peterchen Feb 08 '11 at 17:14
7

I suspect that it can also fail for some specific types

Pass by reference-to-const is the only passing mechanism that "never" fails. It does not pose any requirements on T, it accepts both lvalues and rvalues as arguments, and it allows implicit conversions.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
5

Thou shalt not wake the dead, but head a similar problem and here's some example code that shows how to use C++11s type traits to deduce whether a parameter should be passed by value or reference:

#include <iostream>
#include <type_traits>

template<typename key_type>
class example
{
    using parameter_type = typename std::conditional<std::is_fundamental<key_type>::value, key_type, key_type&>::type;

 public:
  void function(parameter_type param)
  {
      if (std::is_reference<parameter_type>::value)
      {
          std::cout << "passed by reference" << std::endl;
      } else {
          std::cout << "passed by value" << std::endl;
      }
  }
};

struct non_fundamental_type
{
    int one;
    char * two;
};

int main()
{
  int one = 1;
  non_fundamental_type nft;

  example<int>().function(one);
  example<non_fundamental_type>().function(nft);

  return 0;
}

Hope it helps others with a similar issue.

DennisL
  • 466
  • 3
  • 6
0

Besides what James McNellis wrote, I just want to add that you can specialize your template for reference types (for example like this)

Community
  • 1
  • 1
BЈовић
  • 62,405
  • 41
  • 173
  • 273
0

boost::traits has a type trait that selects the "best" type, based on T:

call_traits<T>::param_type

As already mentioned, there are no template-specific issues.

peterchen
  • 40,917
  • 20
  • 104
  • 186