lol @ templates (working title)

Started by
16 comments, last by Mushu 17 years, 6 months ago
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...
Advertisement
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?
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)
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.
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.
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 :(
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 :)
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]
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 :)
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 ) {//...}
___________________________Buggrit, millennium hand and shrimp!

This topic is closed to new replies.

Advertisement