C++ function pointer optimization

Started by
21 comments, last by NotAYakk 17 years, 10 months ago
A compiler never ever has to inline. A compiler is allowed to inline in those cases.

Because many compilers suck at inlining function pointers, functors are often a useful tool.

struct myfunctor {  int operator()() const { printf("I ran my functor!\n"); return 1200; };};int main() {  myfunctor const func;  k += func();  k += myfunctor()();};


The above code tends to beat the compiler over the head with the "inline this thing!". Sadly, it can require some coding gymnastics.
Advertisement
Quote:Original post by Ro_Akira
Unless Hélas has a meaning of 'Opposite of ' or similar, these two arguments appear to be in conflict.


"Hélas" is accurate (French, I believe), but uncommon in English, and therefore comes off as incorrect spelling. I suggest converting to the more modern day English form "alas", which is the equivalent of "unfortunately" (an expression of regret).

How's that for an off-topic post? :)
"Morituri Nolumus Mori"
Quote:A compiler never ever has to inline. A compiler is allowed to inline in those cases.

Agreed. It's dead handy though when a function can be resolved down to an inlined constant for example, and can prune away whole branches of code.
In addition I'd like anyone to suggest a possible reason, apart from not having to, that MSVC++ appeared to fail to fully inline the LocalPointer mentioned earlier.

Forgive me NotAYakk, but would this be a use of functors closer to that of function pointers and virtual functions given earlier:
struct Functor{	virtual int operator() () const = 0;};struct MyFunctor : public Functor{	int operator() () const { cout << "I ran my functor!" << endl; return 1800; }};struct YourFunctor : public Functor{	int operator() () const { cout << "I ran your functor!" << endl; return 1700; }};int UseFunctor (const Functor& f){	return f ();}


The result doesn't seem to be inlining:
		MyFunctor	my;		YourFunctor	your;		k += UseFunctor (my);0040134C  lea         ecx,[esp+0Ch] 00401350  lea         esi,[eax+eax+1] 00401354  mov         dword ptr [esp+0Ch],offset MyFunctor::`vftable' (403210h) 0040135C  mov         dword ptr [esp+8],offset YourFunctor::`vftable' (403218h) 00401364  call        dword ptr [MyFunctor::`vftable' (403210h)] 0040136A  add         esi,eax 		k += UseFunctor (your);0040136C  mov         eax,dword ptr [esp+8] 00401370  mov         edx,dword ptr [eax] 00401372  lea         ecx,[esp+8] 00401376  call        edx  
Virtual functors are analagous to function pointers, yes.

The typical use of functors as a replacement for function pointers, but allowing inlining, is you template the code that uses the functor.

template<typename functor>void do_something_to_data( data d, functor const& func ) {  func(d);}


Now, the above code works on anything with an operator(), be it a function pointer, a functor, or a virtual functor.

If you use a non-virtual functor, you end up with the code being inlined in effectively every compiler.

This is the method that the STL algorithm functions use, and using a non-virtual functor makes your code easy for the compiler to inline.

Quote:In addition I'd like anyone to suggest a possible reason, apart from not having to, that MSVC++ appeared to fail to fully inline the LocalPointer mentioned earlier.


Probably because MSVC doesn't trust const as much as it is allowed to. :)

Out of curiosity, does:
static SimpleFunctionPointerType const SimpleFunctionGlobalPointer (&SimpleFunction);

make any difference? (doubt it, but I am curious).

I wonder why C++ doesn't have a "compile_time" keyword (incidating that this value can be determined at compile_time, or the function can be evaluated at compile_time if all the arguments are compile_time.)
Quote:The typical use of functors as a replacement for function pointers, but allowing inlining, is you template the code that uses the functor.

Ah, that's cheating ;) Seriously though, templates are great for compile time evaluation for sure. I'd argue though that they're not always a drop in replacement. Especially if some of the calls really can't be determined until run-time.

Quote:Out of curiosity, does:
static SimpleFunctionPointerType const SimpleFunctionGlobalPointer (&SimpleFunction);

make any difference? (doubt it, but I am curious).

I'm afraid not.

Quote:I wonder why C++ doesn't have a "compile_time" keyword (incidating that this value can be determined at compile_time, or the function can be evaluated at compile_time if all the arguments are compile_time.)

I'd argue agaist the introduction of such a thing. It's more work for me :P. The compiler is in a much better position to resolve things to constants I think. I much prefer the compiler finding out all the things that can be resolved, rather than me thinking if there's about 2000 instances that I've missed.
Edit: Unless you mean that you have functions that can be used in templates and what not (Compile time functions in LISP?). One can use a static member in a template (not compiled):
template<int a>struct MyClass{    static const int static_a = a;};template<int b>struct OtherClass{    OtherClass (const MyClass<b>& other) {}};template<typename T>void Use (T& var){    OtherClass<T::static_a> temp (var);    temp.DoStuff ();}int main (){    MyClass var;    Use (var);}


Even inlining is best left to the compiler I think. With the Link Time Code Generation option in MSVC++ 2005 for example, it can do things like inline Getters and Setters, or any other suitable function, that you never bothered to move out of your .cpp file! I think that's pretty cool. That's part of why I'm surprised to find it falls flat on it's face when presented with some of the examples given in this thread :/

Even if all the inlining and resolving took 30 minutes, for a final release build I'd take it.

Thanks for all the valuable insight everyone! Feel free to add/clarify more, for example the Derived example I gave :)
Quote:Original post by NotAYakk
I wonder why C++ doesn't have a "compile_time" keyword (incidating that this value can be determined at compile_time, or the function can be evaluated at compile_time if all the arguments are compile_time.)


It probably will: Generalized constant expressions
Apologies on the confusion on my part NotAYakk.

I've read that document, and for the most part, I like what I see. I ran into the
static const int my_int = numeric_limits<int>::max ();

problem myself just the other day.

I hope it doens't make compiler vendors lazy, and make them think that any possible constant expressions will be declared as such :/
I want to be able to say "I intend this function to be compile_time calculatable. Please scream and yell at me if it isn't."

Sadly, the paper seems to want to restrict what class of functions can be compile_time to a huge extent. Possibly because they want to be nice to compiler writers. :)

You can't even implement a compile_time factorial calculator using their system. No loops or recursion is allowed. :(
Quote:Original post by NotAYakk
You can't even implement a compile_time factorial calculator using their system. No loops or recursion is allowed. :(


Well, you can, only it would be a class, not a function, but I understand what you are saying.

template<int N>class Factorial {public:    enum { value = N * Factorial<N-1>::value };};class Factorial<1> {public:    enum { value = 1 };};

--Michael Fawcett
Quote:Original post by mfawcett
Quote:Original post by NotAYakk
You can't even implement a compile_time factorial calculator using their system. No loops or recursion is allowed. :(


Well, you can, only it would be a class, not a function, but I understand what you are saying.

*** Source Snippet Removed ***


That's using the template system.

The template system is sufficiently powerful(tm), it just has annoying syntax to get at the sufficiently powerful capabilities, which sucks.

I want to write clean C style code that has no side effects, feed it compile-time determined inputs, and have it generate compile-time determined outputs.

For example:
static const int my_int = numeric_limits<int>::max ();

you can "fix" this by using enums for max:
enum{ my_int = numeric_limits<int>::max };

assuming that numeric_limits was written "correctly".

But that isn't what I want.

I want:

compile_time int max( int, int ) { return a>b?a:b; }compile_time int min( int a, int b) { return a<b?a:b; }compile_time int bound( int bottom, int value, int top ) { return max(bottom, min(value, top) ); }compile_time int factorial( int n ) { if (n==0) return 1; return n*factorial(n-1); }compile_time double sin( double radians ); // in a .cpp file somewherecompile_time double cos( double radians ); // in a .cpp file somewherecompile_time double tan( double radians ); // in a .cpp file somewherecompile_time double sqrt( double src ); // in a .cpp file somewhere


at the very least. On top of this, it would be nice to have:

void do_stuff( compile_time int n );


which forces the caller to pass a compile_time value to the function. Such a parameter can be used internally like any other compile-time-determined value.

Of course, I ain't respecting how hard it is to write a compiler when I ask for these sorts of things. :)

This topic is closed to new replies.

Advertisement