/*
Copyright (C) 2013 Tom Bachmann
This file is part of FLINT.
FLINT is free software: you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License (LGPL) as published
by the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version. See .
*/
/*
Demo FLINTXX header to illustrate flintxx extension.
*/
///////////////////////////////////////////////////////////////////////////////
// FAKE C DATA TYPE
// (This would normally reside in foo.h.)
///////////////////////////////////////////////////////////////////////////////
#ifndef FOO_H
#define FOO_H
#include
extern "C" { // usually only #ifdef __cplusplus etc
typedef slong foo;
typedef slong foo_t[1];
static __inline__ void foo_init(foo_t f)
{
*f = 0l;
}
static __inline__ void foo_clear(foo_t f)
{
}
static __inline__ void foo_set(foo_t to, const foo_t from)
{
*to = *from;
}
static __inline__ void foo_set_si(foo_t f, slong e)
{
*f = e;
}
static __inline__ void foo_add(foo_t to, const foo_t e1, const foo_t e2)
{
*to = *e1 + *e2;
}
static __inline__ void foo_add_si(foo_t to, const foo_t e1, slong e2)
{
*to = *e1 + e2;
}
static __inline__ int foo_cmp(const foo_t e1, const foo_t e2)
{
if(*e1 == *e2)
return 0;
return *e1 > *e2 ? 1 : -1;
}
static __inline__ int foo_is_zero(const foo_t f)
{
return *f == 0;
}
static __inline__ void foo_magic(foo_t to, const foo_t from)
{
*to = 2 * (*from) + 1;
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
// C++ wrapper
// (This would normally reside in fooxx.h.)
///////////////////////////////////////////////////////////////////////////////
#ifndef FOOXX_H
#define FOOXX_H
#include
#include "flintxx/expression.h"
#include "flintxx/flint_classes.h"
namespace flint {
// fooxx_expression is an "all-purpose" expression template class. In
// principle, both Operation and Data can be arbitrary types (Data has to be
// copy constructible), but in this generality the objects will be not much
// use. In practice, Operation is an empty type, which is just used as a "tag",
// and Data is a rather primitive type holding essentially just some payload.
// Even more practically speaking, the only instantiations the FLINT developer
// should have have to make explicitly are when Operation is
// operations::immediate.
// The flintxx library will create other instantiations automatically, with
// more complicated Data arguments, and different Operation-s.
template
class fooxx_expression
// In order for the flintxx library to do its work, your class must derive from
// flint::expression. If your class has just two template parameters Operation
// and Data, then the following line is sufficient.
: public expression, Operation, Data>
{
public:
// This line is formulaic, and just makes the base class available.
// The typedef is used by the FLINTXX_DEFINE_* macros below, and is
// necessary because of namespace injection bugs in gcc<4.5.
typedef expression,
Operation, Data> base_t;
// The next two lines are formulaic, and most likely required in any
// concrete class.
FLINTXX_DEFINE_BASICS(fooxx_expression)
FLINTXX_DEFINE_CTORS(fooxx_expression)
// This line enables reference types for your class. The second argument is
// the underlying C type (note this is foo, not foo_t). The third argument
// is the name under which to make the underlying C type available.
// All of fooxx, fooxx_ref and fooxx_srcref will have methods _foo() which
// can be used to manipulate the underlying C data type.
FLINTXX_DEFINE_C_REF(fooxx_expression, foo, _foo)
// Now custom methods can be added. The typical pattern is to call a C
// function with argument this->evaluate()._foo(). The evaluate() method is
// inherited from the expression class (this is why it needs to be
// qualified by "this"). It obtains a reference to self if self is an
// immediate object, and otherwise evaluates self into a temporary
// immediate object.
// If you leave out the evaluate() step, then the method will only work
// on immediates (which may be desirable).
bool is_zero() const {return foo_is_zero(this->evaluate()._foo());}
};
// This is formulaic. The class fooxx will be an instantiation of
// fooxx_expression, with Operation operations::immediate and Data
// detail::foo_data. We need to forward-declare this because of cyclic
// dependencies among the immediate types (e.g. fooxx_srcref can be
// constructed from fooxx, and vice versa).
namespace detail {
struct foo_data;
}
// This line just carries out the plan of definition of fooxx explained above.
typedef fooxx_expression fooxx;
// If you want reference types (i.e. if you had FLINTXX_DEFINE_C_REF above),
// these lines are again formulaic.
typedef fooxx_expression > fooxx_ref;
typedef fooxx_expression > fooxx_srcref;
namespace detail {
// We now define the actual immediate Data type. This is not just foo_t (the
// underlying C data type), because want it to behave nicely "in a C++ world".
struct foo_data
{
// In general, your data type can contain members and member types in any
// way you want. However, to work with the automatic reference type system,
// the following three lines are necessary.
foo_t inner;
typedef foo_t& data_ref_t;
typedef const foo_t& data_srcref_t;
// Default constructor. If this is not provided, fooxx will not be default
// constructible (this is OK but requires some additional care, see e.g.
// padicxx).
foo_data() {foo_init(inner);}
// Destructor. You most likely want this.
~foo_data() {foo_clear(inner);}
// Copy constructor. You must provide this.
foo_data(const foo_data& o)
{
foo_init(inner);
foo_set(inner, o.inner);
}
// Instantiation from srcref. This is basically the same as the copy,
// constructor, but unfortunately has to be repeated. This also takes care
// of instantiation from ref, since ref->srcref is an implicit conversion
// path.
foo_data(fooxx_srcref r)
{
foo_init(inner);
foo_set(inner, r._foo());
}
// Now you can add more constructors, or in fact any methods you like.
// This one allows constructing fooxx directly from long, int,
// unsigned short etc.
template
foo_data(T t,
typename mp::enable_if >::type* = 0)
{
foo_init(inner);
foo_set_si(inner, t);
}
};
} // detail
// By now our data type is instantiable, but nothing can be done with it.
// The flintxx library would be able to create expression templates involving
// fooxx, but will not do so because it has no way of evaluating them. We
// need to provides evaluation (and other) *rules* to the library. These
// (have to) live in namespace flint::rules.
//
// All possible rules are defined in flintxx/rules.h.
namespace rules {
// These two lines are convenient, are not formulaic except that they are used
// in all code below.
#define FOOXX_COND_S FLINTXX_COND_S(fooxx)
#define FOOXX_COND_T FLINTXX_COND_T(fooxx)
// Define a conditional assignment rule. The general pattern is
//
// FLINT_DEFINE_DOIT_COND2(name, cond1, cond2, eval).
//
// This will define a "doit" rule for "name", which takes one input and
// one output argument. The result looks something like
//
// template
// struct assignment and cond2 are satisfied>
// {
// static void doit(T& to, const U& from)
// eval;
// };
//
// In our case, we are defining an assignment rule, i.e. an explanation on
// how to execute operator=. If the right hand side is an expression template,
// flintxx will automatically evaluate it first. Thus we need only treat the
// case where the LHS is fmpzxx or fmpzxx_ref, and the RHS is fmpzxx, fmpzxx_ref
// or fmpzxx_srcref. This is precisely what the conditions FOOXX_COND_T
// and FOOXX_COND_S (conditions "fooxx target" and "fooxx source") mean.
FLINT_DEFINE_DOIT_COND2(assignment, FOOXX_COND_T, FOOXX_COND_S,
foo_set(to._foo(), from._foo()))
// This line defines assignment of integral PODs to fooxx. Since the underlying
// C library only defines fooxx_set_si, we can only safely allow this if the
// right hand side can always be losslessly converted into a signed long,
// so we use the condition traits::fits_into_slong. Traits are defined all
// throughout flintxx, but the most general purpose ones (like fits_into_slong,
// is_unsigned_integer etc) can be found in flintxx/traits.h
FLINT_DEFINE_DOIT_COND2(assignment, FOOXX_COND_T, traits::fits_into_slong,
foo_set_si(to._foo(), from, 1))
// We now define evaluation rules. In full generality, the rule evaluation<...>
// can be used to define how to evaluate any kind of expression. But this is
// difficult to use. Moreover, much evaluation logic is shared among most
// data types. For example, to evaluate an expression like a + b + c,
// one typically first has to evaluate (say) a + b into a temporary t, and then
// evaluate t + c. The only step that is specific to fooxx here is how to
// add two immediates.
// For this reason, flintxx has special convenience forms of the evaluation
// rule, called binary and unary expressions. Defining a binary expression
// f(x, y) tells flintxx how to evaluate operation "f" on types "x" and "y",
// typically immediates. Then flintxx will figure out how to evaluate the
// arguments into temporaries first etc.
// There is a common special case, when f(x, y) is always the same as f(y, x),
// even though x and y may be of different types. Letting flintxx know of this
// avoids defining the rule both ways round.
//
// Here we define a commutative binary expression rule, for operation "plus",
// to be executed on to objects of types T and U, both satisfying FOOXX_COND_S.
// The result is to be of type foooxx (the second argument).
// In this case the types are fully symmetric, so we could have used
// FLINT_DEFINE_BINARY_EXPR_COND2 without adverse effects.
//
// The eval statement should have the effect of to = e1 + e2.
FLINT_DEFINE_CBINARY_EXPR_COND2(plus, fooxx, FOOXX_COND_S, FOOXX_COND_S,
foo_add(to._foo(), e1._foo(), e2._foo()))
// Addation of fooxx and PODs. This time CBINARY instead of BINARY is vital.
FLINT_DEFINE_CBINARY_EXPR_COND2(plus, fooxx, FOOXX_COND_S,
traits::fits_into_slong,
foo_add_si(to._foo(), e1._foo(), e2))
// Next we define relational operators. A convenient way of doing so is using
// a "cmp" function, which is handily provided by the underlying C library.
// This has a somewhat peculiar signature, so cannot be defined using one of
// the standard macros. However, it comes up with many actual FLINT data types,
// so we have a special FLINTXX macro just for defining cmp.
FLINTXX_DEFINE_CMP(fooxx, foo_cmp(e1._foo(), e2._foo()))
// Now we define a rule how to print fooxx. There is no macro for this, because
// normally instead we define conversion to string, and flintxx takes care of
// printing. However, the C library for fooxx provides neither printing nor
// conversion to string, so we have to do our own implementation.
template
struct print >::type>
{
static void doit(const T& i, std::ostream& o)
{
o << *i._foo();
}
};
} // rules
// By now fooxx is a pretty passable wrapper type. In fact the only thing left
// to do is to expose foo_magic. This is a special function which can be
// executed on instances of foo, and yields another instance of foo. It is
// essentially just another unary expression, just with an unusual name, so
// this is how we treat it.
// This line introduces a new type of unary operation, called "magic_op",
// together with a function flint::magic(T), which creates expression templates
// with this new operation. In principle, any expression template data type is
// now allowed to define rules how to performa magic on itself.
FLINT_DEFINE_UNOP(magic)
// Finally, we need to explain how to perform magic on flintxx. This is again
// a rule.
namespace rules {
// The pattern should be familiar by now.
FLINT_DEFINE_UNARY_EXPR_COND(magic_op, fooxx, FOOXX_COND_S,
foo_magic(to._foo(), from._foo()))
} // rules
} // flint
#endif
///////////////////////////////////////////////////////////////////////////////
// Example program
///////////////////////////////////////////////////////////////////////////////
using namespace flint;
int
main()
{
fooxx a, b(4);
fooxx_ref ar(a);
fooxx_srcref br(b);
ar = 1 + br + 1; // a=6
std::cout << magic(a + (-1)) << '\n'; // 2*(6-1)+1 = 11
return 0;
}