Sign in to follow this  
lhead

Trick with C++ static function pointers

Recommended Posts

Hey why does this not work??
Quote:
#include<iostream>
using namespace std;

//------------------------------------------------------------------------------

struct A {
  typedef void (*functionpointertype) ();
  const static functionpointertype foo = 0;
};

struct B {
  static void foo() {
    cerr << "\nB\n";
  }
};

//------------------------------------------------------------------------------

void do_some_calculations() {
  // really intensive stuff I'd like to skip if possible
}

template<class T> void run() {
  //
  if(T::foo) {
    do_some_calculations();
    T::foo();
  } else {
    cerr << "\nA\n";
  }
}

//------------------------------------------------------------------------------

int main() {
  run<A>();
  run<B>();
  return 0;
}
The message that my compiler returns (stupid DOS console, MinGW g++)
Quote:
E:\code\mesh-alg>g++ test_functiontest.cpp
test_functiontest.cpp:8: error: invalid in-class initialization of static data member of non-integral type `void (* const)()'
test_functiontest.cpp: In function `void run() [with T = A]':
test_functiontest.cpp:36:   instantiated from here
test_functiontest.cpp:25: error: `foo' is not a member of `A'
test_functiontest.cpp:27: error: `foo' is not a member of `A'
test_functiontest.cpp: In function `void run() [with T = B]':
test_functiontest.cpp:37:   instantiated from here
test_functiontest.cpp:25: warning: the address of `static void B::foo()', will always evaluate as `true'
Yes, obviously there's a problem with the initialization.. But what would be more interesting to me is a better solution for this issue: There are n different candidate classes for the template argument of run<T>(). Some of them (like A) don't have the member function foo() defined, others (like B) have. The code in the if-block should be run only if the member function 'foo' is defined in the template argument. I don't want to define a boolean variable that I would have to set manually for each of those classes. I had some different ideas that also don't work, but first I'd like some feedback on this one.. Regards, lhead

Share this post


Link to post
Share on other sites
The first error, and many of the ones after it, are because of this line:
const static functionpointertype foo = 0;

You can't initialize static members that way. What you need to do is split it apart:
struct A {
typedef void (*functionpointertype) ();
const static functionpointertype foo;
};

// Someplace else in code, at the global scope (not in a function)
const functionpointertype A::foo = 0;


That should fix most, if not all, of your errors. Try making that change first and then post any remaining errors/warnings that you get.

Share this post


Link to post
Share on other sites
Now it works as expected :) thanks ApochPiQ!
Quote:
E:\code\mesh-alg>g++ test_functiontest.cpp
test_functiontest.cpp: In function `void run() [with T = B]':
test_functiontest.cpp:38: instantiated from here
test_functiontest.cpp:26: warning: the address of `static void B::foo()', will always evaluate as `true'

E:\code\mesh-alg>a.exe

A

B

E:\code\mesh-alg>

I hope this is really a solution to the issue I described above.. in a sense that the if-queries are evaluated at compile time, so a lot can be optimized away.

And I still get the stupid warning.. I know why, but don't know what to do about it. It doesn't hurt here, but it will do if included in a bigger piece of code.

Share this post


Link to post
Share on other sites
The warning you get makes more sense to me now that I've had a chance to properly look over the code.

Edit: ignore the following description - I thought you said you didn't know why this was happening. My mistake [smile]

Basically, you need to keep in mind that a template doesn't represent code - a template represents instructions for generating code. So when you instantiate the run() function with the template parameter B, you're basically generating this code:

struct B {
static void foo() {
cerr << "\nB\n";
}
};

void runB() {
if(B::foo) {
do_some_calculations();
B::foo();
} else {
cerr << "\nA\n";
}
}



Essentially, B will never be false, so the if() is redundant. The compiler recognizes this and generates a warning.

It sounds to me like you want to make sure that the template parameter T always has a valid member foo, and throw a runtime error if not. Except you're overloading foo in a hackish way, by sometimes expecting it to be a function pointer, and sometimes a direct call. This is dangerous and makes your code harder to understand.

The cleanest solution is to get rid of the ability to have foo() be a member function, and just wrap objects that "look like" struct B in an interface that looks like struct A and redirects to B::foo via a function pointer. I'm not entirely clear on the problem you're aiming to solve, though, so that may not work out for you as nicely as it sounds in my head [smile]

Share this post


Link to post
Share on other sites
Maybe the following pseudocode can explain the intention.


struct A {
// doesn't have a member 'foo'
};

struct B {
static void foo() {cerr << "\nB\n";}
};

//------------------------------------------------------------------------------

template<class T> void run() {
//
// I want the following if-query to be evaluated at compile time.
if(T has member 'foo') {
do_some_calculations();
T::foo();
} else {
cerr << "\nA\n";
}
}

//------------------------------------------------------------------------------

int main() {
run<A>();
run<B>();
return 0;
}


Share this post


Link to post
Share on other sites
Quote:
Original post by ApochPiQ
You shouldn't need to do that at all. If you try to instantiate run() with a T that has no member foo, you get a compiler error, since foo is explicitly referenced inside of run().

Yes, that's the reason why it's pseudocode :) In the pseudocode world, the compiler would know that the if-block is unreachable, and ignore the error.

In the original code, the compiler is fed with a fake 'foo' member to play with, until it can remove the unreachable code.

The most obvious solution would be to set a const boolean, but I would have to set it manually - for lots of classes like 'A' and 'B', and for lots of member functions that may be defined or not, independent of each other.

Share this post


Link to post
Share on other sites
Sounds like polymorphism would be a good candidate to solve this:

typedef void (*funcptr) ();

class ThingBase
{
public:
virtual DoFoo()
{
std::cout << "Didn't do any foo." << std::endl;
}
};

class Thing : public ThingBase
{
public:
Thing() : m_FP(NULL) {}
Thing(funcptr foo) : m_FP(foo) {}
virtual DoFoo()
{
if(m_FP)
m_FP();
else
ThingBase::DoFoo();
}

protected:
funcptr m_FP;
};


class A : public ThingBase
{
};

class B : public Thing
{
};

void bar()
{
std::cout << "Did some Foo, bar." << std::endl;
}

int main()
{
A thinga;
B thingb(bar);

thinga.DoFoo();
thingb.DoFoo();
}


Share this post


Link to post
Share on other sites
Urg. I think what's sought here is that SFINAE trickery that I macro-ized a while ago, but as usual I'm apparently the only one who doesn't keep a bookmark for my own work :( Could someone link it in here please? :) (P.S. All I did was set up some macros; the real credit is to Fruny, and before him to a bunch of Boost mailing list people, or so I understand.)

Share this post


Link to post
Share on other sites
I've found a solution that looks much more elegant!
However, it requires to derive all the candidates from a base class, where the empty versions of each member function have to be defined.

#include<iostream>
using namespace std;

//------------------------------------------------------------------------------

struct Empty {
static void foo0() {}
static void foo1() {}
static void foo2() {}
};

//------------------------------------------------------------------------------

struct A : Empty {
//
static void foo0() {
cerr << "\nA::foo0()";
}
static void foo1() {
cerr << "\nA::foo1()";
}
};

struct B : Empty {
//
static void foo2() {
cerr << "\nB::foo2()";
}
};

//------------------------------------------------------------------------------

void do_some_calculations() {
// really intensive stuff I'd like to skip if possible
}

template<class T> void run() {
//
if(T::foo0!=Empty::foo0) T::foo0();
else cerr << "\nfoo0 not implemented.";
//
if(T::foo1!=Empty::foo1) T::foo1();
else cerr << "\nfoo1 not implemented.";
//
if(T::foo2!=Empty::foo2) T::foo2();
else cerr << "\nfoo2 not implemented.";
//
cerr << "\n";
}

//------------------------------------------------------------------------------

int main() {
run<A>();
run<B>();
return 0;
}



@ApochPiQ: Runtime polymorphism is something I tried to avoid, because it adds an unnecessary bloat. I hope this one does not, and allows the compiler to remove the unreachable code.

@Zahlman, Extrarius: I need more time to study your ideas...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this