Sign in to follow this  
Mushu

lol @ templates (working title)

Recommended Posts

Okay, so I'm having a problem in my code. I don't really know how to describe it, since I'm not really sure wtf is going on. Essentially, the wrong template parameters are being choosen or something. Arg. I'm going to try to re-read the template chapter of the book, but doubt I'll finish it. Anyway, my problem. Hrm, where to begin. Basically I'm goofing around and writing a non-intrusive python embedding library (if you're one of those people who enjoy context). Basically, we have a happy function, say - int callPyFunc( char* arg ); We want to pass this function pointer to a function which exposes it to python - python::expose<int, char*>( "call", &callPyFunc ); Now, I know I don't need the template parameters there, its just for purposes of clarity. Right. So this is where things get icky... The corresponding templated overload of expose looks like this -
template < class R, class A1 >
void expose( const std::string& name, R( *func )( A1 ) ) {
	details::CFuncStub stub;
	stub.func = func;
	stub.call = &details::_ArgParser<R>::parse<A1>; // WHAT.
	details::_addCFunc( name, stub );
}
The problem occurs on the commented line. This is where things get icky. Really icky. Actually, let me explain/justify part of this mess first. Basically, we cast the function pointer to a void*. We then cache a pointer to a corresponding function which takes a void*, casts it to the right pointer and does crazy Python stuff and stuff. That's the commented line. Ack, lemmie just post the monster. Trimmed down, of course.
template < class R >
struct _ArgParser {
	template < class A1 >
	static PyObject* parse( PyObject* args, void* func ) {
		typedef R ( *FUNCTYPE )( A1 );
		FUNCTYPE realfunc = ( FUNCTYPE ) func;
		const char* name = NULL;
		A1 a1;

		if ( PyArg_ParseTuple( args, _getFmtString<char*,A1>().c_str(), &name, &a1 ) ) {
			return _returnObject<R>( realfunc( a1 ) );
		}

		return NULL;
	}

	template <>
	static PyObject* parse<void>( PyObject* args, void* func ) {
		typedef R ( *FUNCTYPE )( void );
		FUNCTYPE realfunc = ( FUNCTYPE ) func;
		return _returnObject<R>( realfunc() );
	}
};
Go ahead and digest that. Now, given that the function we're exposing has the signature int(*)(char*) and explicitly call python::expose<int,char*>, we assume that stub.call gets set to - &details::_ArgParser<int>::parse<char*> Sadly, this is not the case. It gets set to the explicitly defined &details::_ArgParser<int>::parse<void> Which breaks it [sad]. How do I fix this behavior? The only thing I can think of is to wrap in another struct like _ArgParser which handles the special case where the function takes no parameters. _ArgParser handles the special case where the function returns nothing. Nested templated structs seems a bit ... messy. Anything better out there? And I've no idea why its even doing this...

Share this post


Link to post
Share on other sites
What happens when you take out the template specializiation for void in _ArgParser?

I'm not exactly sure, but it could have something to do with having templated functions within a templated class/struct. Never done that before but why not get rid of the _ArgParser class and have to global functions with two template arguments?

Share this post


Link to post
Share on other sites
Quote:
Original post by okonomiyaki
What happens when you take out the template specializiation for void in _ArgParser?

It works perfectly. Except then you can't have functions which take no arguments, and is thus not really an acceptable solution :S

Quote:
I'm not exactly sure, but it could have something to do with having templated functions within a templated class/struct. Never done that before but why not get rid of the _ArgParser class and have to global functions with two template arguments?

Short answer - because you can't partially specialize template functions, only templated classes. Hold on, lemmie post this then type out the explanation, because I feel I have to justify such craziness.

Okay, so basically, you have two cases - functions that return void and those that don't. If we look at the code I posted originally, you'll notice the line -

return _returnObject<R>( realfunc( a1 ) );

What this does is, essentially, cache the value of the function, then give those returned values back to python. Lets take a look at the code -
template < class R > PyObject* _returnObject( R val ) {
PyObject* ret = Py_BuildValue( _getFmtString<R>().c_str(), val );
Py_DECREF( ret );
return ret;
}

Seems okay, right?

Well, in most situations, yes. In fact, it works fine in every situation except the case when R = void. In this case, you're declaring a variable, var of type void, and passing it around and such. Which is completely and totally illegal.

Now, there's another problem, which is the Python syntax for returning void, which can be shown in the sister function -
inline PyObject* _returnNone() {
Py_INCREF( Py_None );
return Py_None;
}

Actually, I can't remember the exact reason that explicit specialization didn't work here, but I'm almost positive its because you can't define a function which takes a void parameter. Or something messy like that.

Anyway, from here you're basically required to have two separate instances of each parse function. Since we're only dealing with the return type parameter, we need partial specialization, which requires templated classes.

And eeeeeeek.

(Also, if you have a better solution for this part, I'm all ears :3)

Share this post


Link to post
Share on other sites
Quote:
Original post by Mushu
Quote:
Original post by okonomiyaki
What happens when you take out the template specializiation for void in _ArgParser?

It works perfectly. Except then you can't have functions which take no arguments.


Interesting. So for some reason the template specialization is forcing it to void.

Quote:

Short answer - because you can't partially specialize template functions, only templated classes. Hold on, lemmie post this then type out the explanation, because I feel I have to justify such craziness. LOL :D


I did not know that. :) Learn something new every day. What about having the two class functions with two arguments? I wonder if it's just being picky about the way you have it set up.

Share this post


Link to post
Share on other sites
Quote:

(Also, if you have a better solution for this part, I'm all ears :3)


You could try including the template param R in the function declaration, might make it a little clearer and somehow solve your problem too. Unfortunately I'm not really a master of templates so my help is somewhat limited. I enjoy hacking through these issues though (is that sadistic?).

To make sure you aren't having any problems with how your template is set up, I tested this program, and it worked fine:


template<class R>
struct TemplateClass {
template<class A1>
static void print() {
R val1;
A1 val2;
printf("Printing non-void...\n");
}

template<>
static void print<void>() {
R val1;
printf("Printing void...\n");
}

};

int _tmain(int argc, _TCHAR* argv[])
{
TemplateClass<char*>::print<int>();
return 0;
}




This prints "non-void" and if you change the int type a void it correctly prints "void". I'll look at the issue a little more but I'm sure somehow else can point out something else by then... good luck!

Edit: You might also want to check what type A1 is in the "expose" function to make sure it's what you're expecting - and not void.

Share this post


Link to post
Share on other sites
Quote:
Original post by okonomiyaki
Quote:

(Also, if you have a better solution for this part, I'm all ears :3)


You could try including the template param R in the function declaration, might make it a little clearer and somehow solve your problem too.

Yeah, that's the way I had it set up the first time around. The problem is when R is void, and it breaks. The solution to this is specialization, but you can't partially specialize function templates, which presents the problem when you have arguement types to pass -

template < class R > R func();
template<> void func< void >(); // okay

template < class R, class A1 > R func( A1 );
template < class A1 > void func< void,A1 >( A1 ); // uhh, what?


And I decided that declaring void-returning functions to be evil wasn't the optimal solution.

lolol I guess I was wrong?

Quote:
Edit: You might also want to check what type A1 is in the "expose" function to make sure it's what you're expecting - and not void.

Yeah, I thought that might be the case, so I explicitly called expose with template parameters. I honestly don't know how to check the parameters at run-time with VC7, that's probably something I should look into...

EDIT: Nope, the proper parameters are passed to the expose function :(

Share this post


Link to post
Share on other sites
Quote:
Original post by Mushu
template < class A1 > void func< void,A1 >( A1 ); // uhh, what?


I could swear you could do that. Partial template specialization? I can't even say that it'll begin to fix your problem though, so I wouldn't worry about it.

Quote:

Yeah, I thought that might be the case, so I explicitly called expose with template parameters. I honestly don't know how to check the parameters at run-time with VC7, that's probably something I should look into...

EDIT: Nope, the proper parameters are passed to the expose function :(


Ah, ok. typeid() is one way of checking, but you probably found that out. Hmm... I'll hash this through. Maybe something will come up when I'm falling asleep :)

Share this post


Link to post
Share on other sites
Oookay, it sounds like this is a compiler issue. matTd just tried the code on VC8 and it worked fine. I'm assuming you're using VC8 too, okonomiyaki, since its working for you too.

Bleeeeehhhhhh.

Well, this kind of limits my options here.

EDIT: Yeah, its a compiler issue. Here's the code he used to test it -

#include <iostream>

typedef int PyObject;

namespace python
{

namespace details
{

template < class R >
struct _ArgParser {
template < class A1 >
static PyObject* parse( PyObject* args, void* func ) {
/*typedef R ( *FUNCTYPE )( A1 );
FUNCTYPE realfunc = ( FUNCTYPE ) func;
const char* name = NULL;
A1 a1;

if ( PyArg_ParseTuple( args, _getFmtString<char*,A1>().c_str(), &name, &a1 ) ) {
return _returnObject<R>( realfunc( a1 ) );
}

return NULL;*/


std::cout << "In templated single arg parse()" << std::endl;
return 0;
}

template <>
static PyObject* parse<void>( PyObject* args, void* func ) {
/*typedef R ( *FUNCTYPE )( void );
FUNCTYPE realfunc = ( FUNCTYPE ) func;
return _returnObject<R>( realfunc() );*/


std::cout << "In expl template instd no arg parse()" << std::endl;
return 0;
}
};

}

template < class R, class A1 >
void expose( const std::string& name, R( *func )( A1 ) ) {
/*details::CFuncStub stub;
stub.func = func;*/

struct monkey
{
PyObject * (*call)(PyObject *, void *);
} stub;

stub.call = &details::_ArgParser<R>::parse<A1>; // WHAT.
//details::_addCFunc( name, stub );

stub.call(0, 0);
}

}

int fn (char *p)
{
return 123;
}

int main ()
{
python::expose<int, char *>("fn", fn);
return 0;
}


On VC8, you get the proper response. I got "no args parse" in VC7.1

[bawling]

Share this post


Link to post
Share on other sites
Quote:
Original post by Mushu
Oookay, it sounds like this is a compiler issue. matTd just tried the code on VC8 and it worked fine. I'm assuming you're using VC8 too, okonomiyaki, since its working for you too.

Bleeeeehhhhhh.

Well, this kind of limits my options here.

EDIT: Yeah, its a compiler issue. Here's the code he used to test it -

*** Source Snippet Removed ***


Ew. Sorry to hear it man. That is ugly territory... if you are having problems with that, it's definitely not gonna work with partial template specialization. Hope you're able to figure something out.

Oh, and yeah, I'm using VC8 :)

Share this post


Link to post
Share on other sites
Instead of partial template specialization, try simple overloading. This should even work in 7.1.

template < class R, class A1 >
static PyObject* parse( PyObject* args, void* func ) {
//...
}

template < class R >
static PyObject* parse( PyObject* args, void* func ) {
//...
}


Share this post


Link to post
Share on other sites
Quote:
Original post by samv
Instead of partial template specialization, try simple overloading. This should even work in 7.1.
*** Source Snippet Removed ***

Yeah, that's what I did at first, but it had the same problem.

I think I'll twist the compiler's arm a little. Its gonna get messy in here :(

Share this post


Link to post
Share on other sites
Whee! Got it to work. Basically, I made a special case for the expose function using the overload suggestion -
template < class R >
void expose( const std::string& name, R( *func )( void ) ) {
details::CFuncStub stub;
stub.func = func;
stub.call = &details::_ArgParser<R>::parseNoArgs;
details::_addCFunc( name, stub );
}

template < class R, class A1 >
void expose( const std::string& name, R( *func )( A1 ) ) {
details::CFuncStub stub;
stub.func = func;
stub.call = &details::_ArgParser<R>::parse<A1>;
details::_addCFunc( name, stub );
}

See the difference? I took out the code for _ArgParser<R>::parse<void>, and instead made that a separate function entirely. This works because, well, in the case of no arguments we don't need a templated function and blah blah.

Anyway, thanks for all the help guys. Thankfully the solution was a lot cleaner than I thought it might end up being. w00t! :D

Share this post


Link to post
Share on other sites
Good to hear it!

I see that this is the dawn of a new era, where people may begin to say the same things about VC7 that they have been saying about VC6 for some time.

We just made the 7->8 switch at work[smile]

Share this post


Link to post
Share on other sites
Ah, good call. I hadn't thought about overloading the expose function, but I thought about the ArgParse functions and realized that wouldn't work. But that's a good solution.

Share this post


Link to post
Share on other sites
In VC7, if two template functions have the same signature & "name", they are considered identical. (actually, if two functions have the same ARGUEMENTS and name, you are screwed -- I don't think changing just the return value will make two template functions sufficiently different.)

Note that your "fix" is incomplete. Try making a function that returns a double and another that returns on int -- only one version of "parse" will be created (either the int or double one) and will be called in both cases.

Here are two fixes to your problem:

Solution 1:
Add an unused parameter that changes the signature of the two different versions
template < class R >
struct _ArgParser {
template < class A1 >
static PyObject* parse( PyObject* args, void* func, A1* unused = 0 ) {
// body unchanged
}

template <>
static PyObject* parse<void>( PyObject* args, void* func, void* unused = 0 ) {
// body unchanged
}
};


The second solution is less hacky:


template < class R >
struct _ArgParser {
template<typename A1>
struct RetVal {
static PyObject* parse( PyObject* args, void* func ) {
// body unchanged
};
};

template <>
struct RetVal<void> {
static PyObject* parse( PyObject* args, void* func ) {
// body unchanged
};
};
};


And change your access of parse to:
details::_ArgParser<R>::Retval<A1>::parse




Either that, or stop using VC7. :)

[Edited by - NotAYakk on September 30, 2006 10:04:30 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by NotAYakk
Note that your "fix" is incomplete. Try making a function that returns a double and another that returns on int -- only one version of "parse" will be created (either the int or double one) and will be called in both cases.

Wait, what? I don't see how that's possible.

Two separate class definitions should be created, _ArgParser<int> and _ArgParser<double>, because those represent the return type of the functions (which isn't a true return type - they all return a PyObject*).

I'm really sleepy right now. Anyway, each templated function (even though they have the same signature) resides in a separate class namespace, so I would assume it to be different.

If you're saying that

&_ArgParser<int>::parseNoArgs == &_ArgParser<double>::parseNoArgs

then, like PM me or something and I'll stop using VC7 altogether and burn the CDs in a giant pyre of shame. Because that's a stupidity that I honestly can't live with.

Anyway. I probably misread some part of your post because I'm really sleepy, so I'm going to quickly test to see if different overloads of _returnObject<R> are called, because I'm kind of new to templates and don't really know how to check their parameters at runtime with the debugger.

Want to sleeeeeppp....

EDIT:

It appears that two separate instances of _returnObject are created in your example, _returnObject<int> and _returnObject<double>, suggesting that the int and double versions of parse (didn't use the NoArgs one) are indeed not the same function. lolz, I probably am completely not understanding what you're trying to say then :(

Share this post


Link to post
Share on other sites
Quote:
I'm really sleepy right now. Anyway, each templated function (even though they have the same signature) resides in a separate class namespace, so I would assume it to be different.

If you're saying that

&_ArgParser<int>::parseNoArgs == &_ArgParser<double>::parseNoArgs

then, like PM me or something and I'll stop using VC7 altogether and burn the CDs in a giant pyre of shame. Because that's a stupidity that I honestly can't live with.


Not quite -- VC7 understood templated namespaces where fundamentally different.

What it didn't understand was that templated functions where fundamentally different, even if they had the same arguements (and maybe return type).

What I'm saying is that:


Try the following in VC7:


template<typename A>
void foo() {
printf("default version! %d\n", sizeof(A));
}

template<>
void foo<int>() {
printf("int version!\n %d\n", sizeof(A));
}

template<>
void foo<double>() {
printf("double version!\n %d\n", sizeof(A));
}


int main() {
foo<char>();
foo<int>();
foo<double>();
foo<long>();
}




Do you get what you expect? Last I recall using VC7, the above failed horribly.

What I'm saying is that
&foo<long> == &foo<char>


The "correct" output to the above program would be:

default version! 1
int version!
4
double version!
8
default version! 4


(assuming you are compiling on a typical win32 system)

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