Sign in to follow this  
imi

SFINAE and member template functions?

Recommended Posts

Hi, can I test on the existance of member function templates using SFINAE? I tried and get compile errors with VC. Seems like it does not accept pointer to instantiated member function templates as template arguments..? My "normal" class trait looks like this:
template<class T, void (T::*)()> struct Empty {};

template<class T> float checkForFunction(...);
template<class T> char checkForFunction(Empty<T, &T::testFunction>*);
template<class T> struct Traits
{ enum { hasFunction = sizeof(checkForFunction<T>(0)) == 1 }; };

struct Foo { void testFunction() {} };
struct Bar {};

int main()
{
	cout << Traits<Foo>::hasFunction << endl;
	cout << Traits<Bar>::hasFunction << endl;
}




output:
1
0
It works like a charm. However, when I use it to test for template member functions, it fails. Anyone knows why? Don't they have addresses too?
template<class T, void (T::*)()> struct Empty {};

template<class T> float checkForFunction(...);
template<class T> char checkForFunction(Empty<T, &T::testFunction<int> >*);
template<class T> struct Traits
{ enum { hasFunction = sizeof(checkForFunction<T>(0)) == 1 }; };

struct Foo { template<class T> void testFunction() {} };
struct Bar {};

int main()
{
	cout << Traits<Foo>::hasFunction << endl;
	cout << Traits<Bar>::hasFunction << endl;

	cin.get();
}




Output:
0
0
Any help? Anyone doing do compile-time checking for functions in another way? I tried to get something out of Boost.Concept but couldn't find a way to call functions conditional. Maybe boost::enable_if, but I haven't figured out yet how this fits in here.. (I guess enable_if can be used *after* I got this code above running to conditional enable a function based on the traits.) Ciao, Imi. Edit: Just remembered: Functions whose pointers are used as template argument must not have static linkage, right? Do generated member function templates have static linkage? If so, can I change this somehow? (Was just a guess..)

Share this post


Link to post
Share on other sites
I'm guessing this has something to do with the way template-generated function overloads are looked up in the class namespace. I was able to make it work in VS2005 with a minor tweak:

#include <tchar.h>
#include <iostream>

template<class T, class T2, void (T::*)(T2)> struct Empty {};

template<class T, class T2> char checkForFunction(Empty<T, T2, &T::testFunction >*);
template<class T, class T2> float checkForFunction(...);
template<class T, class T2> struct Traits
{ enum { hasFunction = (sizeof(checkForFunction<T, T2>(0)) == 1) }; };

struct Foo { template<class T> void testFunction(T test) {} };
struct Bar {};


int _tmain(int argc, _TCHAR* argv[])
{
std::cout << Traits<Foo, int>::hasFunction << std::endl;
std::cout << Traits<Bar, int>::hasFunction << std::endl;
}



Note that I had to explicitly use the T2 template type in the testFunction in order to make sure the function gets instantiated correctly.


I've run this through a few tests and it seems to always give correct results, but I make no guarantees [smile]

Share this post


Link to post
Share on other sites
That would be bad news, because actually my function template argument is not a type, but an int. I just simplified to a type, as well.. it is more common. ;)


struct Foo { template<int MAX_SIZE> void testFunction(/* Doh! and now??? */) {} };


Beside.. your solution wouldn't make sense at all. &T::testFunction is not a function pointer, but a member function template itself. Sounds even stranger that this can be a template argument matching "void (T::*)()"... :-O

Ciao, Imi.

Share this post


Link to post
Share on other sites
Templated member functions are just automatically compile-time-generated overloads. So I don't see any issue with the method I posted... I'm still asking the compiler for a member function pointer, it just happens to be a function pointer to a function I didn't (explicitly) write.


Can you post a more complete set of code reflecting what you want to do?

Share this post


Link to post
Share on other sites
For the lack of time, here's (line 120+) my implementation of "has_member_XXX", which should recognize every symbol in any class (typedefs, member variables, enums, enum values and other rvalues, member functions (scroll to the bottom for some tests))".

Under the hood it is divided into several tests, and I documented which test can solve for which entity. Sorry for it being a macro, but you can, for evaluation, easily demacrofy it (sidenote: there's no other way to implemented has_member_xxx for any symbol without #define-macros).

Share this post


Link to post
Share on other sites
With my last comment about the "wouldn't make sense", I meant the instantiation of Empty using an address operator on a member function template.


// requires a type and a pointer to member function to T
template<class T, void (T::*)()> struct Empty {};

// T is type, but T::testFunction is not a member function.
// T::testFunction is a member function template!
// &T::testFunction is the address of a template. That's what I find "strange"
template<class T> char checkForFunction(Empty<T, &T::testFunction>*);


Anyway, my later goal is to conditional-call a member function, if it is present and do nothing if it is not present - except that its not a member function, but a member function template, taking an <int> as template parameter. It's for a particle engine I write.

I'd like to try to pass MAX_SIZE as a template argument. It's compile-time constant anyway and enables structs that implement emitParticles to use it in compile time constructs (array indicies and whatever)..

This version doesn't work:

namespace Particle
{
template<class T, class P, void (T::*)(float, P*, typename P::Vertex*, size_t&)> struct EmptyEmitFunction {};

template<class T, class P> float checkForFunction(...);
template<class T, class P> char checkForFunction(EmptyEmitFunction<T, P, &T::emitParticles<0> >*, int, int, int, int);

template<class T, class P> struct Traits {
const static bool hasEmitParticles = sizeof(checkForFunction<T,P>(0,0,0,0,0)) == 1;
};

template<bool, class T, class P> struct Call
{
template<int MAX_SIZE> static void emitParticles(T& t, float elapsed, P* p, typename P::Vertex* v, size_t& size)
{ t.emitParticles<MAX_SIZE>(elapsed, p, v, size); }
};
template<class T, class P> struct Call<false,T, P>
{
template<int> static void emitParticles(T&, float, P*, typename P::Vertex*, size_t&) {}
};

... // later somewhere in the code
template<class P, template<class> class U1>
class Sequence : public U1<P>
{
public:
template<int MAX_SIZE>
void emitParticles(float elapsed, P* p, typename P::Vertex* v, size_t& size)
{
Call<Traits<U1<P>,P>::hasEmitParticles, U1<P>, P>::emitParticles<MAX_SIZE>(*this, elapsed, p, v, size);
}
};
}





That's the current working solution. I just pass the template argument as integer parameter.

namespace Particle
{
template<class T, class P, void (T::*)(float, P*, typename P::Vertex*, size_t&, size_t)> struct EmptyEmitFunction {};
template<class T, class P> float checkForFunction(...);
template<class T, class P> char checkForFunction(EmptyEmitFunction<T, P, &T::emitParticles>*, int, int, int, int);

template<class T, class P> struct Traits {
const static bool hasEmitParticles = sizeof(checkForFunction<T,P>(0,0,0,0,0)) == 1;
};

template<bool, class T, class P> struct Call
{
static void emitParticles(T& t, float elapsed, P* p, typename P::Vertex* v, size_t& size, size_t MAX_SIZE)
{t.emitParticles(elapsed, p, v, size, MAX_SIZE);}
};
template<class T, class P> struct Call<false,T, P>
{
static void emitParticles(T&, float, P*, typename P::Vertex*, size_t&, size_t) {}
};

... // later somewhere in the code
template<class P, template<class> class U1>
class Sequence : public U1<P>
{
void emitParticles(float elapsed, P* p, typename P::Vertex* v, size_t& size, size_t MAX_SIZE)
{
Call<Traits<U1<P>,P>::hasEmitParticles, U1<P>, P>::emitParticles(*this, elapsed, p, v, size, MAX_SIZE);
}
};
}




Please ignore the extra-int,int,int,int parameter to checkForFunction. They are not important here. It's just because I reuse checkForFunction.


The complete header file is here: https://nightwatch.eigenheimstrasse.de/browser/trunk/Nightwatch/ParticleEngine.h


Before anyone makes comments in the direction of: You know the word 'overdesign'? - Yes, I know. It's more some kind of playground for me to tackle how far I can go with generic source code. It's not production code and I am the sole maintainer anyway.. (Yet. MUAHAHAHAHA.. :D)


Ciao, Imi.
PS: I'd appreciate comments like: "Boah, how cumbersome, just use boost::??? or Loki::??? or whatever for super-easy conditional calling of functions" :-)

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
For the lack of time, here's (line 120+) my implementation of "has_member_XXX", which should recognize every symbol in any class (typedefs, member variables, enums, enum values and other rvalues, member functions (scroll to the bottom for some tests))".


Nice. does it do member template functions? :-D

I'd like to try that out. Do you have a link with a copy'n'pasteable version of the source?

Even if not, it's very clear organized and readable. Good job!

Ciao, Imi.

Share this post


Link to post
Share on other sites
Quote:
Original post by imi
I'd like to try that out. Do you have a link with a copy'n'pasteable version of the source?


Never mind, found the link.

I tried it in VC and it doesn't work. First, I had to expand the macro or I got super-strange errors in sourcecodeannotation.h about a type "vc_attributes::YesNoMaybe" :-O

After macro expansion, it compiles, but yield 0 for all structs. I'll look a bit into it and maybe its a silly problem.

Ciao, Imi.

Share this post


Link to post
Share on other sites
minimal code to reproduce the problem under VC:


#include <iostream>

template<typename C>
struct has_member_FOOO {
typedef char Yes;
class No { char c[2]; };

template <int> struct murk { murk(int); };

template <typename S> static Yes test0(murk<sizeof(&S::FOOO)>);
template <typename S> static No test0(...);

enum { test0result = sizeof(Yes) == sizeof(test0<C>(0)) };
};

struct Frob { void FOOO (){} };

int main()
{
std::cout << has_member_FOOO<Frob>::test0result << std::endl;
}




Error message is (line 13 is the "enum { test0result...":

1>test2.cpp(13): error C2070: 'overloaded-function': illegal sizeof operand
1> test2.cpp(20) : see reference to class template instantiation 'has_member_FOOO<C>' being compiled
1> with
1> [
1> C=Frob
1> ]
1>test2.cpp(13): error C2070: 'overloaded-function': illegal sizeof operand


Bummer.. It was a nice approach with the murk<> indirection. Any idea?

Ciao, Imi.
Edit: Looks like VC doesn't support sizeof on members of classes without explicit objects (http://en.wikipedia.org/wiki/C%2B%2B0x#Allow_sizeof_to_work_on_members_of_classes_without_an_explicit_object)

Share this post


Link to post
Share on other sites
I "fixed" the code for VC. It *should* work with GCC as well (I think so.. maybe.. :-O).

And... WOOOOOOOHOOOOOOOOOO! Works with member function templates!

Unfortunaltely, the object under test needs a default constructor now or else it will result in false silently :-(. Very uncool. Well, not a problem for my specific case, still not so cool. But when time comes, VC will support the new C++0x - sizeof as well. :-D


Here's the diff for VC and a test that it works with member template functions:

Edit: Or it would.. but the code and source tags of this forum seem to be confused with the diff. Maybe it's the trailing \ or something. Contact me if you are interested. Basically, I replaced "S::XXX" with "S().XXX" to address the member functions in test0 and test-template (within test2)


Ciao, Imi.

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