Copyright © 2009, Matthew Orlando
This guide presents everything you need to know to get started using IdOp. To see these examples in action, see IdOpExamples.cpp.
In order to use IdOp, you will need the Boost Preprocessor library installed in your include path. Download Boost here.
In your source file you simply need to include IdOp.h to access all the features. All the other necessary headers will be included automatically (IdOpExamples.h is unnecessary for your own projects).
IdOp uses an identifier flanked by two built-in binary operatiors to accomplish the infix notation associated with operators. The the operators and identifier together are called an idop (I like to pronounce it EYE-dee-ahp or EYE-dahp).
leftOperand leftOperator identifier rightOperator rightOperand
\___________________________________/
idop
When leftOperator has equal or greater or equal precedence than rightOperator, the idop is said to be left-handed, and leftOperand is called the primary operand. Conversely, if rightOperator has greater precedence than the leftOperator, the idop is said to be right-handed, and rightOperand is the primary operand.
Note that the preprocessor has no way of determining these precedences automatically. You must specify the handedness of the operators by using the approprate macro (see IdOp Macros). The primary operand determines the default return type for the operation. The secondary operand must be—or must be convertible to—the type of the primary operand.
Here is the canonical example:
foo ^_^ bar
| Left operand |
Left operator |
Identifier | Right operator |
Right operand |
|---|---|---|---|---|
foo |
^ |
_ |
^ |
bar |
| Primary | idop | Secondary | ||
The actual operations are carried out by a functor template, whose name you provide to the IdOp macros when you create the idop. The templates should have a single type parameter, and the operator()’s parameters should be references to this type (you can also use pass-by-value, but then you may miss out on some compiler optimizations). Since the functors will be instantiated as constants, you must also declare the operator() to be const.
Here are the templates we will be using for the examples below:
template<typename T> class Quotient {
public:
T operator()(const T& left, const T& right) const
{ return left / right; }
}; template<typename T> class Contains { public: bool operator()(const T& left, const T& right) const { return std::string(left).find(right) != std::string::npos; } }; template<typename T> class AddLeft { public: T& operator()(T& left, const T& right) const { left += right; return left; } }; template<typename T> class Sum { public: T operator()(const T& left, const T& right) const { return left + right; } }; template<typename T> class Difference { public: T operator()(const T& left, const T& right) const { T val = left - right; return val >= 0 ? val : -val; } }; template<typename T> class ThrowNotEqual { public: void operator()(const T& left, const T& right) const { if (left != right) throw std::runtime_error("left != right"); } }; template<typename T> class AssertNotEqual { public: void operator()(const T& left, const T& right) const { assert(left != right); } }; template<typename T> class Product { public: T operator()(const T& left, const T& right) const { return left * right; } };
IdOp uses several macros to generate the various templates necessary to implement an idop. While this makes it very simple to create idops, if things go wrong the compiler won't be much help. If you make an error in your use of a macro, you’ll likely get messages mentioning the various sub-macros involved (all the macros begin with either BOOST_PP or IDOP). Even if you get the macros right, errors in your usage of the operator or in the construction of your operation templates can make for similarly cryptic error messages. Unfortunately there is no way around these problems. Just keep this in mind when you’re debugging programs built with IdOp.
Creates a simple x-handed idop. No other idops can use the same identifier.
IDOP_CREATE_LEFT_HANDED( leftOperator, identifier, rightOperator, operationType )
IDOP_CREATE_RIGHT_HANDED( leftOperator, identifier, rightOperator, operationType )
IDOP_CREATE_RIGHT_HANDED(^, __, -, Quotient) int main() { std::cout << "50 / 7.0 == " << (50 ^__- 7.0) << std::endl; }
Creates a simple x-handed idop with the specified return type. No other idops can use the same identifier.
IDOP_CREATE_LEFT_HANDED_RET( leftOperator, identifier, rightOperator, operationType, returnType )
IDOP_CREATE_RIGHT_HANDED_RET( leftOperator, identifier, rightOperator, operationType, returnType )
IDOP_CREATE_LEFT_HANDED_RET(<, _contains_, >, Contains, bool) #define contains <_contains_> int main() { if ("Hello, World!" contains "Hello") std::cout << "Everything's fine." << std::endl; else std::cout << "Something went horribly wrong." << std::endl; }
IDOP_OPERAND_TYPE resolves to the name of the template parameter used by IdOp so you can, e.g. return by reference.
IDOP_CREATE_LEFT_HANDED_RET(<, plusequals, >, AddLeft, IDOP_OPERAND_TYPE &) int main() { double baz = 38; const double xyzzy = 2; baz <plusequals> xyzzy <plusequals> xyzzy; std::cout << "The answer to the ultimate question: " << baz << std::endl; }
Specifies a secondary operator and operation to perform. These macros are used as arguments to the IDOP_x_HANDED macros shown below.
IDOP_OPERATION( secondaryOperator, operationType )
IDOP_OPERATION_RET( secondaryOperator, operationType, returnType)
See IDOP_CREATE.
Specifies a primary operator and a set of operations. These macros are used as arguments to IDOP_CREATE shown below.
IDOP_LEFT_HANDED( leftOperator, operation [operation] ... )
IDOP_RIGHT_HANDED( rightOperator, operation [operation] ... )
The operations can be either of the IDOP_OPERATION macros shown above.
See IDOP_CREATE.
Creates a set of idops for a given identifier using an arbitrary number of left and right operators. No other idops can use the same identifier.
IDOP_CREATE( identifier, primaryMacro [primaryMacro] ... )
The primaryMacros can be either of the IDOP_x_HANDED macros shown above.
IDOP_CREATE( _ , IDOP_LEFT_HANDED( - , // x -_^ y <=> x + y IDOP_OPERATION(^, Sum) ) IDOP_LEFT_HANDED( > , // x >_> y <=> abs(x - y) IDOP_OPERATION(>, Difference) // x >_< y <=> if (x != y) throw IDOP_OPERATION_RET(<, ThrowNotEqual, void) ) IDOP_RIGHT_HANDED( - , // x ^_- y <=> assert(x != y) IDOP_OPERATION_RET(^, AssertNotEqual, void) // x |_- y <=> x * y IDOP_OPERATION(|, Product) ) ) int main() { using namespace std; const int foo = 42, bar = 69; cout << "foo -_^ bar == " << (foo -_^ bar) << endl; cout << "foo >_> bar == " << (foo >_> bar) << endl; try { foo >_< bar; cout << "foo >_< bar succeeded unexpectedly"; } catch (runtime_error ex) { cout << "foo >_< bar failed as expected. Message: " << ex.what() << endl; } cout << "Doing foo ^_- bar assertion...\n"; foo ^_- bar; cout << "foo |_- bar == " << (foo |_- bar) << endl; }