|
||||||||||||||||||
Add Forum to Favorites | Send Topic To a Friend | View Forum FAQ | Track this topic |
Last Thread Next Thread ![]() |
| Binding Operators is bad for your sanity. |
|
![]() Deyja Member since: 3/5/2002 From: Stafford, VA, United States |
||||
|
|
||||
| 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)[i]; } //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 |
||||
|
||||
![]() WitchLord Moderator - AngelCode Member since: 3/27/2000 From: Sao Paulo, Brazil |
||||
|
|
||||
| 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: // Mutator asMETHODPR(myclass, operator[], (unsigned int), char&) // Inspector asMETHODPR(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 |
||||
|
||||
![]() Deyja Member since: 3/5/2002 From: Stafford, VA, United States |
||||
|
|
||||
| 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] |
||||
|
||||
![]() Deyja Member since: 3/5/2002 From: Stafford, VA, United States |
||||
|
|
||||
#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); |
||||
|
||||
![]() Rain Dog Member since: 7/21/2004 From: Mesa, AZ, United States |
||||
|
|
||||
| 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[i]; } 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. |
||||
|
||||
![]() Deyja Member since: 3/5/2002 From: Stafford, VA, United States |
||||
|
|
||||
| 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. |
||||
|
||||
![]() Deyja Member since: 3/5/2002 From: Stafford, VA, United States |
||||
|
|
||||
| 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. |
||||
|
||||
All times are ET (US)![]() |
Last Thread Next Thread ![]() |
|