17

I want to write a template function that does something with a std::stack<T> and an instance of T, e.g.:

template<class StackType> inline
bool some_func( StackType const &s, typename StackType::value_type const &v ) {
  // ...
}

The reason I pass v by reference is of course to optimize for the case where StackType::value_type is a struct or class and not copy an entire object by value.

However, if StackType::value_type is a "simple" type like int, then it's of course better simply to pass it by value.

The question is: for a type such as int that would become int const& as a formal argument in the above function, will the compiler optimize away the reference and simply pass it by value?

Paul J. Lucas
  • 6,895
  • 6
  • 44
  • 88

4 Answers4

10

I look in gcc optimization options here http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

And actually there is an option for your case:

-fipa-sra

Perform interprocedural scalar replacement of aggregates, removal of unused parameters and replacement of parameters passed by reference by parameters passed by value.

Enabled at levels -O2, -O3 and -Os

As far as I know, -O2 is usual option for release build on linux.

So, the short answer is: one of good compiler does

Community
  • 1
  • 1
Oleksandr Pryimak
  • 1,561
  • 9
  • 11
7

Though I haven't actually tested any compilers for this, I doubt it. You're assuming that passing a const reference is indistinguishable from just passing a value, but that's not true, and the compiler shouldn't assume that it is.

Since the reference is const, your function can't modify the value through it, but other parts of the code may have access to the original (non-const) variable, and might modify it. Your function might call some other function which changes it, for example, or there might be another thread running concurrently that does it.

If something else modifies that original variable, your function, with its reference, should see the new value. If the compiler replaced the reference with a copy, the function would still see the old value.

You might be interested in Boost's call traits library, though. It provides a template type call_traits<T>::param_type which is a const reference for "big" types that you don't want to copy, and a value for "small" types where a copy would be more efficient. Basically, what you're wanting the compiler to do implicitly, you can do explicitly with your code.

Wyzard
  • 33,849
  • 3
  • 67
  • 87
  • 3
    You are not right about threads. Compiler optimizer often breaks code which works with bad "lock-free" (naive) data structures. Compiler doest need to think about other threads! – Oleksandr Pryimak Jan 26 '12 at 10:52
  • Also, simple functions dont call other functions at all – Oleksandr Pryimak Jan 26 '12 at 10:54
  • @AlexandrPriymak, the question didn't specify what code is in the function, so it might be one that calls other functions. And C++11 includes a concurrency-aware memory model and standardized threading facilities, so a C++11 compiler must consider what other threads might do even if a C++98 compiler doesn't. – Wyzard Jan 26 '12 at 15:17
  • 1
    An implication is that the function is written as if it were passed a const reference, i.e., it can't change the value. The question is whether a compiler can optimize away the reference (typically implemented as a pointer) and simply pass by value for template inline functions. Thanks for the pointer to call traits. – Paul J. Lucas Jan 26 '12 at 23:58
  • Ah, I overlooked the fact that it's inline. – Wyzard Jan 27 '12 at 01:00
  • @Wyzard I am not talking about C++11 because now most code are writing under C++98. Even C++11 consider other threads only a few modules like – Oleksandr Pryimak Jan 27 '12 at 14:23
  • @Wyzard: Threading is **not** a concern here. Alexandr is correct. – user541686 Jul 04 '14 at 02:25
7

Whenever the compiler is able to inline, the resulting cross-procedural optimization will eliminate the expense of finding and passing an address.

For non-inline-able function calls, the reference probably will be implemented as a pointer, not pass-by-value.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
3

This is MSVC2010 64bit compiler, full optimizations:

int foo( int i )
{
 int a = i + i;

 return ( a );
}
//Disassembly:
//00000001`3ff11c50 8d0409          lea     eax,[rcx+rcx]
//00000001`3ff11c53 c3              ret

int bar( int const & i )
{
 int a = i + i;

 return ( a );
}
//Disassembly:
//00000001`3ff11c10 8b01            mov     eax,dword ptr [rcx]
//00000001`3ff11c12 03c0            add     eax,eax
//00000001`3ff11c14 c3              ret

In the first case, value is passed on RCX, in the second - address passed on RCX. Of course, if functions are inlined, as Ben said, then the produced code might be absolutely the same in both cases.

Passing instances of "small" classes (say, class only has an int data member and trivial constructor, bitwise copy-constructor etc) is faster by value. Compiler will optimize it as, essentially, just passing a copy of int. So, you can overload your function, as Wyzard pointed out, using some type traits. Something like that:

template< typename PType >
PType Func( typename std::enable_if< ( sizeof( PType ) > sizeof( size_t ) ), PType const & >::type f )
{
 //large things: reference
}

template< typename PType >
PType Func( typename std::enable_if< ( sizeof( PType ) <= sizeof( size_t ) ), PType  >::type f )
{
 //small things: value
}
lapk
  • 3,838
  • 1
  • 23
  • 28
  • 1
    Note that your experimental analysis is for "normal" functions, not templated functions. That's a critical difference; a normal function can't (in general) have its argument list adjusted by compiler optimization, whereas a templated function is necessarily inlinable and thus can. – Brooks Moses Jan 26 '12 at 06:46
  • @BrooksMoses Function inlining does not depend on it being templated or non-templated function. Simply the fact that one has templated function's definition in each compilation unit it used in, does not guarantee its inlining at all... If my "experimental" functions simultaneously declared & defined in one compilation unit, they will be inlined. And the code will be the same. And the other way around, even "simple" templated functions might not be inlined. – lapk Jan 26 '12 at 06:59
  • Yes, I oversimplified. Nonetheless, if your "experimental" functions are declared and defined in a single compilation unit, but are declared in such a way that they are exported from that functional unit, then they cannot be inlined. And you gave no indication that they were either static or that you were compiling a self-contained whole program, which means they were necessarily exported. – Brooks Moses Jan 26 '12 at 20:24
  • @BrooksMoses The point is: if function is inlined, then there is no "passing" arguments to function, so there is no need to worry about argument being passed by reference or value. The question that OP author asks ONLY makes sense in case function is NOT inlined. I gave an example of what's happening in this case. I could've made a templated function in such way that is not going to be inlined by compiler. But for clarity I made a simple non-templated function that is not inlined, to show how compiler optimizes such non-inlined functions when arguments are passed by reference and by value. – lapk Jan 26 '12 at 20:45