Binding Operators is bad for your sanity.

Started by
5 comments, last by Deyja 17 years, 11 months ago
Go try binding operator[] to angelscript without using a wrapper. Frustrated yet? I thought so. The problem in this case is the preprocessor. That is part of the motivation for a chunk of code I call the "Angelscript Class Binding Utility Templates". The rest of the motivation has to do with the macro asFUNCTION. Because asFUNCTION takes just the name of the function, C++ can't do any overload resolution. If theres more than one function with that name - as is nearly always the case with operators - it fails. Angelscript supplies the macro asFUNCTIONPR to help. But sometimes, that still isn't enough. Take the following perfectly valid C++

foo& operator+=(foo& lhs, const foo& rhs);
Yes, that is operator +=. And no, it is not a member function. You cannot bind this function without a wrapper. AngelScript expects this operator bound as a member, and thus requires either the asCALL_THISCALL or asCALL_CDECL_OBJLAST calling convention. We've established two situations where a wrapper is absolutly neccessary. Why don't we go ahead and banish asFUNCTIONPR completely? And wouldn't it be nice if we also got C++ overload resolution and automatic conversions too? Well, we can! We can just wrap everything! Now, that's a bit of a pain in the ass. But simple templates can automate it for us. Please note that these templates are all implicitly inline - except that we're taking their address, so they can't be inlined. However, if the actual operator they are wrapping is inline, it can be inlined into the function, making it just as effecient as if you had bound the operator directly.

/*
   Angelscript Class Binding Utility Templates
   Copyright (c) 2006 Anthony Casteel

   This software is provided 'as-is', without any express or implied 
   warranty. In no event will the authors be held liable for any 
   damages arising from the use of this software.

   Permission is granted to anyone to use this software for any 
   purpose, including commercial applications, and to alter it and 
   redistribute it freely, subject to the following restrictions:

   1. The origin of this software must not be misrepresented; you 
      must not claim that you wrote the original software. If you use
	  this software in a product, an acknowledgment in the product 
	  documentation would be appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and 
      must not be misrepresented as being the original software.

   3. This notice may not be removed or altered from any source 
      distribution.

   Anthony Casteel
   jm@omnisu.com
*/

/* Important Notes --

	Motivation --
		
		Binding operators to angelscript is HARD. If there are overloads 
		involved, you have to specify exactly which overload you mean
		using complicated macros that the preprocessor often chokes on.
		For example, try binding operator[]. The preprocessor often fails
		when it encounters that [] construct in the name of the function.
		These templates solve that problem by invoking C++ overload
		resolution system. These templates wrap the operator and make
		unqualified calls to the operator, allowing the compiler to choose
		the best match using argument dependant lookup and overload
		resolution. All variable types are templated for maximum flexibility.
		The templates do not enforce proper reference usage; you must
		explicitly add & to your template parameters. No quarantees are
		made as to how well this will work if you don't use cannonical
		operator signatures.

		AngelScript requires that some operators - such as += - be bound as
		members. C++ makes no such restriction. In the case where operator+=
		is declared globally, as T& operator+=(T& lhs, const T& rhs), there
		is no way to directly bind it to AngelScript, as AngelScript requires
		this operator to be bound with either the asCALL_THISCALL or
		asCALL_CDECL_OBJLAST convention. A wrapper is neccessary in this
		case. These templates can also be used to automate the generation
		of such a wrapper.

	If your class is not const correct, these templates will not work. 

	Notice that the wrappers do NOT take anything by reference. If a parameter
	should be passed by reference, include the reference in the type when 
	instantiating the template. This decision was made because, by default, 
	AngelScript cannot use C++ references directly. To use references properly,
	you must either use AutoHandles or turn on asUNSAFE_REFERENCES.

	The constructors-with-arguments wrappers take up to only 5 arguments.
	This is easily extendable.

	Naming Conventions --

		When a return value is appropriate, it's type is first.
		Constructors take the class first, followed by the parameters.

		Binary operators take the return type, the first parameter, and the
		second, in that order.
		Binary Operator: R operater???(F lhs, S rhs)

		Binary Member operators take template parameters in the same order as
		global Binary Operators.
		Binary Member Operator: R S::operator???(F lhs)

	Operator-> is experimentally wrapped in MemberSelection. This may or may 
	not work.

	Some operators not supported --
		()		- Would require something similiar to Construct#, if supported by AS
		++		- Not supported by AS (Implementations still provided)
		--		- Not supported by AS (Implementations still provided)
		*		- Not supported by AS
		&		- Don't overload this. ...
		(type)	- Not supported by AS
		,		- Don't overload this.
		new		- AS provides alternate mechanism?
		new[]	- See above
		delete	- See above
		delete[]- See above
        ->		- Experimental Implementation


	Example Usage --
		
		Binding a few operators for the type 'foo'

		//copy constructor
		engine->RegisterObjectBehaviour(
			"foo",
			asBEHAVE_CONSTRUCT,
			"void f(const foo&)",
			asFUNCTION((Wrap::Construct1<foo,foo&>)),
			asCALL_CDECL_OBJLAST);

		//operator+
		engine->RegisterGlobalBehaviour(
			asBEHAVE_ADD,
			"foo f(const foo&,const foo&)",
			asFUNCTION((Wrap::Add<foo,foo&,foo&>)),
			asCALL_CDECL);

		//operator+=
		engine->RegisterObjectBehaviour(
			"foo",
			asBEHAVE_ADD_ASSIGN,
			"foo& f(const foo&)",
			asFUNCTION((Wrap::AddAssign<foo&,foo,foo&>)),
			asCALL_CDECL_OBJLAST);

*/


#ifndef JM_CLASS_BINDING_UTILITY_TEMPLATES_H
#define JM_CLASS_BINDING_UTILITY_TEMPLATES_H

namespace Wrap
{
	//Constructors (with up to 5 parameters)
	//Use asCALL_CDECL_OBJLAST
	template<typename T> void Construct(T* ptr) { new (ptr) T(); }
	
	//Can also be used as copy constructor
	template<typename T, typename P1> 
	void Construct1(P1 p1, T* ptr) { new (ptr) T(p1); }

	template<typename T, typename P1, typename P2> 
	void Construct2(P1 p1, P2 p2, T* ptr) { new (ptr) T(p1,p2); }

	template<typename T, typename P1, typename P2, typename P3> 
	void Construct3(P1 p1, P2 p2, P3 p3, T* ptr) { new (ptr) T(p1,p2,p3); }

	template<typename T, typename P1, typename P2, typename P3, typename P4> 
	void Construct4(P1 p1, P2 p2, P3 p3, P4 p4, T* ptr) { new (ptr) T(p1,p2,p3,p4); }

	template<typename T, typename P1, typename P2, typename P3, typename P4, typename P5>
	void Construct5(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, T* ptr) { new (ptr) T(p1,p2,p3,p4,p5); }

	//Bind as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F,typename S> R Assign(const S rhs, F* ptr) { return (*ptr) = rhs; }
	template<typename F> void Destroy(F* ptr) { ptr->~F(); }

	//Comparison operators.
	//Bind as global behaviors; use asCALL_CDECL
	template<typename F,typename S> bool Equal(const F lhs, const S rhs) { return lhs == rhs; }
	template<typename F,typename S> bool NotEqual(const F lhs, const S rhs) { return lhs != rhs; }
	template<typename F,typename S> bool GreaterEqual(const F lhs, const S rhs) { return lhs >= rhs; }
	template<typename F,typename S> bool LessEqual(const F lhs, const S rhs) { return lhs <= rhs; }
	template<typename F,typename S> bool Greater(const F lhs, const S rhs) { return lhs > rhs; }
	template<typename F,typename S> bool Less(const F lhs, const S rhs) { return lhs < rhs; }

	//Unary operators.
	//Bind as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F> R Not(F* thisp) { return !(*thisp); }
	template<typename R,typename F> R Negate(F* thisp) { return -(*thisp); }
	template<typename R,typename F> R Positate(F* thisp) { return +(*thisp); }
	template<typename R,typename F> R BinaryComplement(F* thisp) { return ~(*thisp); }

	//Binary operators. Operator, Assign variety. 
	//Bind operator as global behavior; use asCALL_CDECL
	//Bind assign variety as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F,typename S> R Add(const F lhs, const S rhs) { return lhs+rhs; }
	template<typename R,typename F,typename S> R AddAssign(const S rhs, F* thisp) { return (*thisp) += rhs; }

	template<typename R,typename F,typename S> R Subtract(const F lhs, const S rhs) { return lhs-rhs; }
	template<typename R,typename F,typename S> R SubtractAssign(const S rhs, F* thisp) { return (*thisp) -= rhs; }

	template<typename R,typename F,typename S> R Multiply(const F lhs, const S rhs) { return lhs*rhs; }
	template<typename R,typename F,typename S> R MultiplyAssign(const S rhs, F* thisp) { return (*thisp) *= rhs; }

	template<typename R,typename F,typename S> R Devide(const F lhs, const S rhs) { return lhs/rhs; }
	template<typename R,typename F,typename S> R DevideAssign(const S rhs, F* thisp) { return (*thisp) /= rhs; }
	
	template<typename R,typename F,typename S> R Modulus(const F lhs, const S rhs) { return lhs%rhs; }
	template<typename R,typename F,typename S> R ModulusAssign(const S rhs, F* thisp) { return (*thisp) %= rhs; }

	template<typename R,typename F,typename S> R ShiftLeft(const F lhs, const S rhs) { return lhs << rhs; }
	template<typename R,typename F,typename S> R ShiftLeftAssign(const S rhs, F* thisp) { return (*thisp) <<= rhs; }

	template<typename R,typename F,typename S> R ShiftRight(const F lhs, const S rhs) { return lhs >> rhs; }
	template<typename R,typename F,typename S> R ShiftRightAssign(const S rhs, F* thisp) { return (*thisp) >>= rhs; }

	template<typename R,typename F,typename S> R BinaryAnd(const F lhs, const S rhs) { return lhs & rhs; }
	template<typename R,typename F,typename S> R BinaryAndAssign(const S rhs, F* thisp) { return (*thisp) &= rhs; }

	template<typename R,typename F,typename S> R BinaryOrRight(const F lhs, const S rhs) { return lhs | rhs; }
	template<typename R,typename F,typename S> R BinaryOrAssign(const S rhs, F* thisp) { return (*thisp) |= rhs; }

	template<typename R,typename F,typename S> R BinaryXorRight(const F lhs, const S rhs) { return lhs ^ rhs; }
	template<typename R,typename F,typename S> R BinaryXortAssign(const S rhs, F* thisp) { return (*thisp) ^= rhs; }

	//These binary operators do not have an assign variety
	template<typename R,typename F,typename S> R LogicalAndRight(const F lhs, const S rhs) { return lhs && rhs; }
	template<typename R,typename F,typename S> R LogicalOrRight(const F lhs, const S rhs) { return lhs || rhs; }

	//Index operator
	//Bind as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F,typename S> R Index(const S i, F* thisp) { return (*thisp); }

	//Increment operators (Not supported by AngelScript)
	//Bind as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F> R PreIncrement(F* thisp) { return ++(*thisp); }
	template<typename R,typename F> R PostIncrement(F* thisp) { return (*thisp)++; }
	template<typename R,typename F> R PreDecrement(F* thisp) { return --(*thisp); }
	template<typename R,typename F> R PostDecrement(F* thisp) { return (*thisp)--; }

	//MemberSelection operator - MAY OR MAY NOT WORK.
	//Bind as type behavior; use asCALL_CDECL_OBJLAST
	template<typename R,typename F> R MemberSelection(F* thisp) { return thisp->operator->(); }
}

#endif

Advertisement
You've made some cool templates. I'll add a link to this thread from the Wiki.

However, I don't understand what you think is so difficult with the operator [] in particular. It would be registered just like other member operators, and the address of the function would be taken with the call to:

// MutatorasMETHODPR(myclass, operator[], (unsigned int), char&)// InspectorasMETHODPR(myclass, operator[], (unsigned int) const, char)


Regards,
Andreas


AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

The specific problem I had was with std::string. I could never get the preprocessor to get past that [] sitting in there. And it's even worse if you happen to be using an std::basic_string that's not the default std::string typedef, like I am. :/ At any rate; you can just drop the whole header into the wiki. In fact, I'll go do that. :)

[edit]
Or it could have been because I was using the entirety of the asMETHODPR macro as a parameter in my OWN macro... I'll post my binding of std::string in a moment, after I strip all the custom-allocator crap.
[/edit]
#include <string>#include "angelscript/autobind.h"namespace{	std::string StringFactory(unsigned int length, const char *s)	{		return std::string(s,length);	}};REGISTER_COMPLETE_BASIC_TYPE("string",std::string);REGISTER_TYPE_BEHAVIOR("string",asBEHAVE_ADD_ASSIGN,"string& op_addassign(const string&)",asFUNCTION((Wrap::AddAssign<std::string&,std::string,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_EQUAL,"bool op_equal(const string&,const string&)",asFUNCTION((Wrap::Equal<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_NOTEQUAL,"bool op_notequal(const string&, const string&)",asFUNCTION((Wrap::NotEqual<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_LEQUAL,"bool op_lessequal(const string&, const string&)",asFUNCTION((Wrap::LessEqual<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_GEQUAL,"bool op_greaterequal(const string&, const string&)",asFUNCTION((Wrap::GreaterEqual<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_LESSTHAN,"bool op_less(const string&, const string&)",asFUNCTION((Wrap::Less<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_GREATERTHAN,"bool op_greater(const string&, const string&)",asFUNCTION((Wrap::Greater<std::string&,std::string&>)),asCALL_CDECL);REGISTER_BEHAVIOR(asBEHAVE_ADD,"string op_add(const string&, const string&)",asFUNCTION((Wrap::Add<std::string,std::string&,std::string&>)),asCALL_CDECL);REGISTER_TYPE_BEHAVIOR("string",asBEHAVE_INDEX,"uint8& op_index(uint)",asFUNCTION((Wrap::Index<char&,std::string,unsigned int>)),asCALL_CDECL_OBJLAST);REGISTER_METHOD("string", "uint length()", asMETHOD(std::string,size), asCALL_THISCALL);REGISTER_STRING_FACTORY("string",asFUNCTION(StringFactory),asCALL_CDECL);
I had a problem trying to bind operator[] using templates. I had a function like this:

in essence this:

template<class IndexType, class ReturnType, class ObjectType>
ReturnType Indexer(IndexType i, ObjectType o)
{
return o;
}

But I could never get it to resolve the 'ReturnType' template. The compiler always balked about not being able to find the function.

I was using asFUNCTIONPR macro btw.

I will have to retry it with your binder now, Deyja.

EDIT: It appears now that the error could have been in the use of asFUNCTIONPR, as Deyja appears to be using asFUNCTION and passing template parameters, while I was doing asFUNCTIONPR(MyFunc, (int, int, vector), int) or something similar.
Yes. The whole point of that is to avoid trying to explicitly choose the exact overload using asFUNCTIONPR. It looks like you are choosing an exact overload, but you aren't. The template parameters specify the interface the overload must have - C++'s own overload resolution chooses the best function. You also get much better error messages.

Remember the extra () around the macro argument. asFUNCTION(Wrap::Something<One,Two,Three>) will be interpretted as asFUNCTION( (Wrap::<One) , (Two) , (Three>) ). Doubling the parens like asFUNCTION((stuff)) fixes that.
Oh. And I will add support for const objects if theres a demand for it. It doesn't apply to all operators though, and I'll have to think for a bit to see how it will work.

This topic is closed to new replies.

Advertisement