1

I have to show functions that are not possible to inline in c++.

To check this I have flag -Winline set.

With recursive functions I am able to generate a function that can not be inlined.

But I try the same with inheritance and "virtual" keyword. But I can not get the compiler to complain that something is not possible to inline.

I know that this topic is covered a lot already. But I just did not find a working example. Is my compiler "to clever" :-)

I tried it with this:

class virt1
{
public:
    virt1(){};
    inline virtual int virtFunc(int a){ return a*a; };
    virtual ~virt1(){};
};

class virt2 : public virt1
{
public:
    virt2(){};
    inline virtual int virtFunc(int a){ return a+a;};
    virtual ~virt2(){};
};

void testVirtFunc(virt2 &obj)
{
     std::cout << obj.virtFunc(2);
}
billz
  • 44,644
  • 9
  • 83
  • 100
Michael
  • 706
  • 9
  • 29
  • Try `void testVirtFunc(virt1 &obj)` – agbinfo Nov 12 '13 at 23:25
  • 4
    The `inline` keyword has almost nothing to do with whether your compiler actually inlines a function. – aschepler Nov 12 '13 at 23:25
  • `virt2` is the most derived type. Try this: `void testVirtFunc(virt1& obj)`. To expand on @ashepler's comment: `inline` in C++ is mostly concerned with the linker, not the compiler. – IInspectable Nov 12 '13 at 23:26
  • possible duplicate of [Are inline virtual functions really a non-sense?](http://stackoverflow.com/questions/733737/are-inline-virtual-functions-really-a-non-sense) – Jim Lewis Nov 12 '13 at 23:26
  • 1
    @aschepler: True, but it has a lot to do with whether `-Winline` will produce a warning if a function can't be inlined. – Mike Seymour Nov 12 '13 at 23:27
  • 1
    If you take the documentation literally, then you'll get a warning if the function itself can never be inlined; not if any particular call can't be. Virtual functions can be inlined, when they're not called virtually, so perhaps this will never trigger the warning. (I've no idea whether this interpretation is actually how the warning is implemented, though). – Mike Seymour Nov 12 '13 at 23:32
  • @IInspectable: But the compiler doesn't know that `virt2` is the most derived type. Another translation unit could define a further derived type, overriding `virtFunc`, and call `testVirtFunc` with one of those. – Mike Seymour Nov 12 '13 at 23:33
  • Thanks for comments! `void testVirtFunc(virt1& obj)` does not give an error neither! And yes, the `inline` command is necessary to "force" the error message! Just seen that eclipse tells me, that `virtFunc` of `virt2` overrides `virt1::virtFunc`... Why is that and could this be the problem? – Michael Nov 12 '13 at 23:34
  • @MikeSeymour True, I hadn't considered this scenario. This makes me wonder how a compiler can actually honor the `inline` keyword here. Other than link-time optimization I cannot think of a way to solve this, and gcc is not a linker... Can you verify whether or not the function actually does get inlined, inspecting the object code, Michael? – IInspectable Nov 12 '13 at 23:44
  • @IInspectable: A virtual function can be inlined either when the compiler knows the dynamic type (because it's called on an object, or a reference or pointer whose derivation from an object the compiler can see), or when you specify a non-virtual call to a particular override, e.g. `obj.virt2::virtFunc(2)`. A virtual call on an arbitrary reference or pointer can only be resolved at run time; even the linker couldn't help in general. – Mike Seymour Nov 13 '13 at 00:30
  • @ MikeSeymour: You were right, it did not inline, but did not warn, because in principle is inlinable. Found out by deassembling... – Michael Nov 13 '13 at 13:01

2 Answers2

6

There's no such concept as "inlinable" or "non-inlinable" function. There are only inlinable or non-inlinable calls to a function. The property of being declared inline is a property of function itself. The property of being inlinable (or non-inlinable) is a property of a specific call. These are two different concepts.

You seem to be mixing these two completely unrelated concepts.

Each and every function can be declared inline. Virtual functions can be declared inline. Recursive function can be declared inline as well. There's nothing strange about it. Your expectations the this is somehow illegal are completely unfounded. It is always legal to declare a function inline.

Meanwhile, whether the actual calls to these function will be inlined is a completely different, independent question. One call to some function can be inlinable, while another call to the very same function can be non-inlinable. "Being inlinable" is, again, a per-call property.

It is true that a dynamically dispatched virtual call cannot be inlined. Meanwhile, in situations when a smart compiler can properly predict the dynamic type of the object used in virtual call at compile-time, the compiler might generate a direct call to a virtual function (a call that does not use dynamic dispatch). This call can easily be inlined.

For example, given your declarations, this call

virt1 *v = rand() % 2 ? new virt1() : new virt2();
v->virtFunc(5);

cannot be inlined. But these calls

virt2 v2;
v2.virtFunc(6);

virt1* v1 = &v2;
v1->virtFunc(7);

can be inlined without any problems.

An indirect call made through a pointer to a function typically cannot be inlined. But if the compiler somehow knows the exact value of the pointer at compile-time, then the call can be replaced with direct call and inlined.

Recursive function calls can also be inlined (despite your thinking otherwise). They can be inlined to a certain fixed recursion depth. That's how most compilers inline recursive functions: they use depth-limited inlining to "unwrap" recursion just like they unwrap cycles.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Thanks for your detailed answer! I was not precise with my formulation: I actually meant if the function can be called regarding specific calls. By disassembling I also found out, that g++ did not inline, but did also not warn, because the function is in principle inlinable. Which I also could generate and see by disassembling calling the function when not declared virtual. – Michael Nov 13 '13 at 13:06
3

Inline means slightly more than "register" to a compiler. (as in the compiler totally ignored the register keyword long before it was deprecated, not in the sense the commands are similar, they are not, were not even)

Back in the day they were like "I will cherish this knowledge from the user", now they are like "Thanks for your input on this matter" then discard it. They are in a MUCH better position than we are to judge.

A lot of work has gone into reducing the penalty of abstraction. This means the compiler can deduce the type of something far better through some quite convoluted statements than previously possible, putting off that v-table jump. If it can work out what's being called it can probably decide if it's worth inlining and stuff like that.

Template used to imply inline, again if you say inline the compiler humours you like a kid doing a card trick (and getting it wrong).

there's no reason you can't inline a virtual in the case of the compiler being able to determine which method you are calling. Much like how it can optimise out variables, it can optimie out function pointers, and virtual basically hides a structure of virtual pointers.

So back in ye-olden days if I do some_derived_instance.some_vritual_method(); there was no reason that couldn't be inlined. Inline never said "IT MUST ININE" it, like register, just gave the compiler a hint.

To answer the question more directly

Your code is NOT CONVOLUTED enough to confuse the compiler. -Winline issues warnings if a function can't be inlined when the compiler ultimately ends up implementing the call.

You will find it difficult to make code convoluted enough in one code file that it cannot tell this. Alias analysis means a union wont hide it. Compilers have a great understanding of code and can "run code" to an extent.

To really test this, put the code for your classes in one file, and use the base clas in another. Then the compiler cannot know what's going on when it compiles the test function, because the call-site is in another object file.

DO NOT USE LINK TIME OPTIMISATION, The compile will still probably inline (with GCC, they just stream the GIMPLE IR, that has all the information attached to it)

Bottom line

It's a bitch to try and fool a modern compiler.

Re: Recursion

The compiler doesn't optimise code, it optimises some intermediate representation. It can "inline recursions" in the sense it represents a recursive function as a wadge of code at a callsite.

It doesn't have a recursion limit it has a limit to the size of the blob it's willing to create. You can make GCC generate HUGE outputs if you "tune" the inliner to be super aggressive. Think Russian dolls, but with N children each.

Alec Teal
  • 5,770
  • 3
  • 23
  • 50