7

Regarding Function passed as template argument, the community wiki answer provided by Ben Supnik discusses the issue of inlining instantiated function templates.

In that answer is the following code:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b,);
}

int add(int a, b) { return a + b; }

int (* func_ptr)(int, int) = add;

int c = do_op(4,5,func_ptr);

The answer goes on to say this (in regards to the final line, which instantiates the function template do_op):

clearly this is not getting inlined.

My question is this: Why is it clear that this is not getting inlined?

Community
  • 1
  • 1
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181

4 Answers4

8

What he's saying (I think) is that the add function is not getting inlined. In other words, the compiler might inline do_op like this:

int c = func_ptr(4, 5);

but it won't also inline add like this:

int c = 4 + 5;

However, he might be wrong in that simple example.

Generally, when you call a function through a pointer, the compiler can't know (at compile-time) what function you'll be calling, so it can't inline the function. Example:

void f1() { ... }
void f2() { ... }

void callThroughPointer() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f();
}

Here, the compiler cannot know whether callThroughPointer will call f1 or f2, so there is no way for it to inline either f1 or f2 in callThroughPointer.

However, if the compiler can prove at compile-time what function will be called, it is allowed to inline the function. Example:

void f1() { ... }
void f2() { ... }

void callThroughPointer2() {
    int i = arc4random_uniform(2);
    void (*f)() = i ? f2 : f1;
    f = f1;
    f();
}

Here, the compiler can prove that f will always be f1, so it's allowed to inline f1 into callThroughPointer2. (That doesn't mean it will inline f1…)

Similarly, in the example you quoted in your post, the compiler can prove that func_ptr is always add in the call to do_op, so it's allowed to inline add. (That doesn't mean it will inline add…)

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
3

When calling a function through a function pointer, the compiler is highly unlikely to avoid the call through the function pointer. Only if the compiler can prove that it knows what the function pointer is being initialized with and that it cannot be changed, it could possibly avoid the function call through the function pointer and, thus, inline the function. In the quoted setup, i.e.,

int (* func_ptr)(int, int) = add;

the function pointer func_ptr is modifiable and the compiler is, thus, not guaranteed that it will never change. As a result, it cannot possibly inline the call to add.

If the snippet of code is, indeed, complete, things happen during initialization and the compiler could actually, indeed, know that func_ptr is initialized to contain add.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
2

I think, the main point was missed in the discussion here so far. First of all, the test code won't even compile due to syntax errors. Probably, the following was meant:

template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
int add(int a, int b) { return a + b; }
int (*func_ptr)(int, int) = add;
int c = do_op(4, 5, func_ptr);
// int c = (*func_ptr)(4, 5);

When compiled, the compiler will issue code for actually calling the add() function. However, when written without the template as int c = (*func_ptr)(4, 5); , the compiler will also issue a call to add(). This is, because func_ptr is defined global in this sample code, and the compiler has to care for the possibility, that some code in another thread modifies func_ptr between its initialization and its subsequent use. But this is a property of the globally visible function pointer, and has nothing to do with the template! Except for the name of some local labels, GCC with optimizer on produces exactly the same assembler output for both the templated and non-templated call to add() via func_ptr. The different label name means, that the optimizer has to turn an extra round because of the template, so compile time increases (like with all templates), but code and thus also code run time is identical.

If func_ptr is moved to a local variable inside a function, as in the following example, the compiler can for sure trace all accesses to func_ptr, and as a result optimize out everything and even no longer call the add() function, neither directly nor through the function pointer:

int testf(void) {
  int (*func_ptr)(int, int) = add;
  return do_op(4, 5, func_ptr);
}

So, to sum up things: Function calls via templates don*t stop the optimizer from doing its work. Function pointers can do harm, if their value cannot be determined safely at compile time, but that problem is not worsened, if a template is added.

Kai Petzke
  • 2,150
  • 21
  • 29
1

Why is it clear that this is not getting inlined?

It's not. There's no reason the compiler couldn't inline all of the code in that snippet.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • 1
    There’s no reason it couldn’t but there’s plenty of reasons why it might not even attempt to do that. If I’m not mis-informed then C (and C++) compilers didn’t even attempt tracking function pointers for the purpose of inlining until fairly recently. – Konrad Rudolph Dec 02 '12 at 23:25
  • 1
    @KonradRudolph - sure, but the claim is that **it is clear that this is not getting inlined**. It's not at all clear. – Pete Becker Dec 03 '12 at 13:49