31

Suppose I've got a function functionProxy that takes a generic parameter function and call its operator():

template< typename Function > void functionProxy( Function function ) {
    function();
}

The object passed to it may be:

  • a functor:

    struct Functor {
        void operator()() const {
            std::cout << "functor!" << std::endl;
        }
    };
    
  • a function:

    void function( ) {
        std::cout << "function!" << std::endl;
    }
    
  • a (C++0x) lambda function:

    [](){ std::cout << "lambda!" << std::endl; }
    

int main( )
{
    functionProxy( Functor() );
    functionProxy( function );
    functionProxy( [](){ std::cout << "lambda!" << std::endl; } );
    return 0;
}

Will the compiler be able to inline function within functionProxy in all the above cases?

peoro
  • 25,562
  • 20
  • 98
  • 150

4 Answers4

35

Sure thing.

It knows the value of function is the same as the value it passes it, knows the definition of the function, so just replaces the definition inline and calls the function directly.

I can't think of a condition where a compiler won't inline a one-line function call, it's just replacing a function call with a function call, no possible loss.


Given this code:

#include <iostream>

template <typename Function>
void functionProxy(Function function)
{
    function();
}

struct Functor
{
    void operator()() const
    {
        std::cout << "functor!" << std::endl;
    }
};

void function()
{
    std::cout << "function!" << std::endl;
}

//#define MANUALLY_INLINE

#ifdef MANUALLY_INLINE
void test()
{
    Functor()();

    function();

    [](){ std::cout << "lambda!" << std::endl; }();
}
#else
void test()
{
    functionProxy(Functor());

    functionProxy(function);

    functionProxy([](){ std::cout << "lambda!" << std::endl; });
}
#endif

int main()
{
    test();
}

With MANUALLY_INLINE defined, we get this:

test:
00401000  mov         eax,dword ptr [__imp_std::endl (402044h)]  
00401005  mov         ecx,dword ptr [__imp_std::cout (402058h)]  
0040100B  push        eax  
0040100C  push        offset string "functor!" (402114h)  
00401011  push        ecx  
00401012  call        std::operator<<<std::char_traits<char> > (401110h)  
00401017  add         esp,8  
0040101A  mov         ecx,eax  
0040101C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401022  mov         edx,dword ptr [__imp_std::endl (402044h)]  
00401028  mov         eax,dword ptr [__imp_std::cout (402058h)]  
0040102D  push        edx  
0040102E  push        offset string "function!" (402120h)  
00401033  push        eax  
00401034  call        std::operator<<<std::char_traits<char> > (401110h)  
00401039  add         esp,8  
0040103C  mov         ecx,eax  
0040103E  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401044  mov         ecx,dword ptr [__imp_std::endl (402044h)]  
0040104A  mov         edx,dword ptr [__imp_std::cout (402058h)]  
00401050  push        ecx  
00401051  push        offset string "lambda!" (40212Ch)  
00401056  push        edx  
00401057  call        std::operator<<<std::char_traits<char> > (401110h)  
0040105C  add         esp,8  
0040105F  mov         ecx,eax  
00401061  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401067  ret  

And without, this:

test:
00401000  mov         eax,dword ptr [__imp_std::endl (402044h)]  
00401005  mov         ecx,dword ptr [__imp_std::cout (402058h)]  
0040100B  push        eax  
0040100C  push        offset string "functor!" (402114h)  
00401011  push        ecx  
00401012  call        std::operator<<<std::char_traits<char> > (401110h)  
00401017  add         esp,8  
0040101A  mov         ecx,eax  
0040101C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401022  mov         edx,dword ptr [__imp_std::endl (402044h)]  
00401028  mov         eax,dword ptr [__imp_std::cout (402058h)]  
0040102D  push        edx  
0040102E  push        offset string "function!" (402120h)  
00401033  push        eax  
00401034  call        std::operator<<<std::char_traits<char> > (401110h)  
00401039  add         esp,8  
0040103C  mov         ecx,eax  
0040103E  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401044  mov         ecx,dword ptr [__imp_std::endl (402044h)]  
0040104A  mov         edx,dword ptr [__imp_std::cout (402058h)]  
00401050  push        ecx  
00401051  push        offset string "lambda!" (40212Ch)  
00401056  push        edx  
00401057  call        std::operator<<<std::char_traits<char> > (401110h)  
0040105C  add         esp,8  
0040105F  mov         ecx,eax  
00401061  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
00401067  ret

The same. (Compiled with MSVC 2010, vanilla Release.)

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    Sounds good in principle, but does it pass the try and see test? I'm unfortunately too busy right now/these days to check. Very curious though as this has occurred to me. – Potatoswatter Feb 01 '11 at 09:23
  • @Potatoswatter: Updated for your viewing pleasure. – GManNickG Feb 01 '11 at 09:33
  • 2
    Uh, nice! Also GCC generates the same code (when optimizations are on). – peoro Feb 01 '11 at 09:49
  • @GMan: I suppose (unless LTO kicks in) that it is necessary for the function to be defined inline (and not in another translation unit) ? – Matthieu M. Feb 01 '11 at 09:52
  • @Matt: That sounds correct to me. I suppose I not only tested whether or not the call was inlined, but the functions as well. I'll test if you'd like, but I suspect that without the ability to inline the called function, it'll do the best it can and inline the call itself. – GManNickG Feb 01 '11 at 10:04
  • although it actually adds to the instruction count, i've seen (specifically, older versions of) GCC pop one liners out of line when instruction counts are already high. if the (expanded) function body is already large, some simple calls may not be inlined. – justin Feb 01 '11 at 10:07
  • @GManNickG Hey Nick, I saw this answer a while ago and I am running some problems with function pointers in MSVC 2012, I was wondering if you could help me! http://stackoverflow.com/questions/16462800/visual-c-not-inlining-simple-const-function-pointer-calls Sorry if it's unpolite I ask it this way, but I didn't know how else to do it. I'd would add a bounty if that helps! Kind regards Christian –  May 09 '13 at 17:13
0

Have tried the following templated pointer-to-lambda code:

volatile static int a = 0;

template <typename Lambda> class Widget {
   public: 
      Widget(const Lambda* const lambda) : lambda_(lambda) { }
      void f() { (*lambda_)(); }
   private:
      const Lambda* const lambda_;
};

int main() {
   auto lambda = [](){ a++; };
   Widget<decltype(lambda)> widget(&lambda);
   widget.f();
}

GNU g++ 4.9.2, Intel icpc 16.0.1, and clang++ 3.5.0 all inlined both widget.f() and (*lambda_)() calls using -O2. That is, a was incremented directly inside main() according to disassembled binaries.

Inlining was applied even with non-const lambda and lambda_ pointers (removing both const).

Ditto with a local variable and lambda capture:

int main() {
   volatile int a = 0;
   auto lambda = [&a](){ a++; };
   ...
Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
0

Possibly. There is no strong reason for or against it, it just depends on what the compiler writers implemented.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
-4

Is the compiler able to inline the calls? Yes.

Will it? Maybe. Check after you know it matters.

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63