See also Readme.
Working versions of
most code examples given in this document are found in
tests/tests.cpp
.
Everything is in the enhance
namespace!
First locate the desired module. Then there several ways
to use Enhance
.
-
Inherit from the one or more of the provided classes or — where inheritance is not possible — use the compiler MACROS for enhancements.
-
Add an
enhance
member function to the class, taking one argument — the so called combiner, which gets passed the list of accessors to be used. In order to use the same accessors for all enhancements, use the templated version:
template<class C> void enhance(C& c) const{
c( <<AccessorList>> );
}
to use different accessor lists for different enhancements, you can add specializations. The following example, gives a different list to all comparison operators:
template<class Op>
void enhance(Comparison<Op,ThisClass>& c) const{
c( <<AccessorList2>> );
}
And another list of accessors for the operator!=
:
void enhance(Unequal<ThisClass>& c) const{
c( <<AccessorList3>> );
}
Instead of inheriting predefined operators, you can implement your own operators, free functions or any other kind of functionality using the combiners directly.
The following works with every combiner. For demonstration purposes,
we use the class Point2D
from above and the LessPW
combiner, which
gives the result of a pointwise comparison the selected fields of two
objects:
// calling of `operator()` with an explicit accessor list:
bool result = LessPW<Point2D>(obj1, obj2)( <<AccessorList>> );
For every combiner, there is a factory function, that automatically deduces the template parameters:
// calling of `operator()` with an explicit accessor list:
bool result = lessPW(obj1, obj2)( <<AccessorList>> );
Implementation details: operator()
is implemented in struct Combiner
.
Combiners can also use a class's enhance
function (as
described above):
// explicitly:
bool result = lessPW(obj1, obj2).callEnhance();
// or implicitly using the provided conversion operator:
bool result = lessPW(obj1, obj2);
// or using the functor:
bool result = LessPW<Point2D>::Functor(obj1, obj2);
The Functor
has its use, e.g. for std::set
with
lexical comparison combiner Less
or
for std::unordered_set
with the Hash
combiner.
// Both types will use the accessor list defined in `Point2D::enhance`
typedef std::set<Point2D, Less<Point2D>::Functor> Point2DSet;
typedef std::unordered_set<Point2D, Hash<Point2D>::Functor> Point2DHashset;
Implementation details: callEnhance
, conversion operator and
Functor
are all implemented in struct Combiner
.
Enhance extracts a collection of values or features from one or more
objects and combines them in some meaningful way (e.g. to calculate
the result of operator<
). How this collection is to be extracted, is
described by a list of Accessors, which can be any of the
following things.
Implementation details: The action of accessors is defined in the
access
function.
Plain data members are accessed using "pointer to members":
&Point2D::x
provides access (as reference if needed) to the x
field of an object
of type Point2D
.
Another possibility are pointer to member functions with zero arguments:
&Point2D::get_quadrant
provides access to the result of int Point2D::get_quadrant()
.
In order to derive enhancements that work on const
objects, make
sure, that the member function works on const
objects, i.e. declare
it like this: int Point2D::get_quadrant() const
.
More flexbility can be obtained through the use of functors and
functions. Any function of one argument and any object with an
operator()
taking one argument, including lambda expressions, can be
used as an accessor. Examples:
// free (template) functions
template<class R> int quadrant(const R&){
...
return 2;
}
// functors
struct IsInQuadrant {
int i;
bool operator()(const Point2D&){
..
return false;
}
};
// lambda expressions
c(quadrant<Point2D>, IsInQuadrant({3}), [](const Point2D& p){return p.x + p.y;});
There a couple of modifiers, that take an accessor and change its action.
Implementation details: Modifiers are either implemented as functors
that work transparently or using wrapper classes that require special
handling like range
.
In order to access the values of an iterable container, use the
range(from, to)
modifier, taking two accessors, from
and to
, as
arguments. All elements between from
and (excluding) to
will be
accessed in order. Example:
struct Vector : Insertable<'<',',',' ','>',Vector,false>{
int data[3];
Vector():data{4,9,2}{}
template<class C> void enhance(C& c) const{
c(range(&Vector::data,
[](const Vector& v){ return v.data+3;}));
}
};
...
cout<< Vector()<<endl;
// prints: <4, 9, 2>
For containers with std::begin
and std::end
implementations, there
exist the following modifiers:
-
begin(accessor)
will evaluate tostd::begin
of the accessor -
end(accessor)
will evaluate tostd::end
of the accessor -
container(accessor)
is a shortcut forrange(begin(accessor), end(accessor))
For objects implementing the compile-time access function std::get
(like std::tuples
, std::arrays
, std::pair
, etc.), there is
range<from,to>(accessor)
. from
defaults to 0
and to
to
std::tuple_size
of the accessor:
struct R2 : Insertable<'{', ',', ' ','}',R2> {
std::tuple<int,double,char> m;
R2() : m({42, 1.1, 'g'}) {}
template<class C> void enhance(C& c) const{
c(range<1,2>(&R2::m),
range<>(&R2::m));
}
};
...
cout<<R2()<<endl;
//prints: {{1.1}, {42, 1.1, g}}
To access the value pointed to by a pointer, use dereference(ac)
,
which will call operator*
on ac
's result.
struct R3 : Insertable<'{', ',', ' ','}',R3> {
std::shared_ptr<int> i;
int* j;
R3() : i(std::make_shared<int>(3)), j(new int(4)) {}
template<class C> void enhance(C& c) const{
c(&R3::i, dereference(&R3::i), dereference(&R3::j));
}
};
...
cout<< R3() <<endl;
//prints: {0x<ADDRESS>, 3, 4}
In order get a non-const
reference to a const
value, use
constCast(ac)
, which will call const_cast<...>
on the ac
's
result.
This can be useful, e.g. in serialization, where an object with
const
member has to be restored from its serialized value, which is
also recommended by the
Boost.Serialization
manual.
This module defines point-wise and lexical comparison combiners and provides classes for operator inheritance. The terminology is adopted from std::tuple.
Point-wise comparison returns only true, if comparison of all pairs of accessors return true.
Lexicographic comparison returns the result of the underlying
comparison operator applied to the first pair of accessors that is not
==
.
All comparison operators are short-circuited; they do not use accessors beyond what is necessary to determine the result of the comparison.
The underlying comparisons are performed using std::equal_to
,
std::not_equal_to
, std::greater
, std::less
, std::greater_equal
and std::less_equal
.
All constructors, factory functions and functors take two
const
references to the objects to be compared:
(const T&, const T&)
The most general combiner (useful for enhance
specialization,
see above) is:
Combiner |
---|
Comparison<Op, T> |
T
stands for the type, on which the comparison should be performed.
In addition to deriving comparison operators for classes, it would be
no problem to provide macros to derive non-member function operators
(i.e. bool operator==(Class, Class)
) or specializations of
std::less
, etc. Happy to merge pull requests, or please open an
issue if interested.
Implementation details: All combiners are type aliases of
BinaryCombiner<Op, const Target>
, where Op
is an instantiation of
either PointwiseComparisonOp
or LexicographicalComparisonOp
.
Combiner | Factory | Inheritable |
---|---|---|
ComparisonPW<Op, T> |
||
Equal<T> |
equal |
EqualComparable<T>::operator== |
Unequal<T> |
unequal |
UnequalComparable<T>::operator!= |
LessPW<T> |
lessPW |
LessComparablePW<T>::operator< |
GreaterPW<T> |
greaterPW |
GreaterComparablePW<T>::operator> |
LessEqualPW<T> |
lessPW |
LessEqualComparablePW<T>::operator<= |
GreaterEqualPW<T> |
greaterPW |
GreaterEqualComparablePW<T>::operator>= |
Combiner | Factory | Inheritable |
---|---|---|
ComparisonLEX<Op, T> |
||
Unequal<T> |
unequal |
UnequalComparable<T>::operator!= |
Less<T> |
less |
LessComparable<T>::operator< |
Greater<T> |
greater |
GreaterComparable<T>::operator> |
LessEqual<T> |
less |
LessEqualComparable<T>::operator<= |
GreaterEqual<T> |
greater |
GreaterEqualComparable<T>::operator>= |
For examples see Readme.
There is currently no general combiner, that can be used in enhance
specialization. Open an issue, if this turns into a problem for you.
All constructors, factory functions and functors take one reference of
the object that will contain the result of adding (subtracting) the
second argument passed by const
reference:
(T&, const T&)
Combiner | Factory | Inheritable |
---|---|---|
ArithmeticComponentwise<Op, T> |
||
Addition<T> |
addition |
Addible<T>::operator+= |
Subtraction<T> |
subtraction |
Subtractable<T>::operator-= |
typedef std::pair<int,double> T;
T p(1, 4.4);
addition(p, {2, 5.5})(&T::first, &T::second);
cout<< p << endl;
//prints: (3, 9.9)
All constructors, factory functions and functors take two const
references to the objects to be multiplied:
(const T&, const T&)
Combiner | Factory | Inheritable |
---|---|---|
ScalarProduct<Scalar, T> |
scalarProduct |
WithScalarProduct<T>::operator* |
Point2D v{2,3}, w{20,30};
cout<< ScalarProduct<double, Point2D>::Functor()(v,w) << endl;
//prints: 130
The combiner's constructor and factory functions take one reference to the object to be multiplied by the scalar passed by (templated and thus collapsable) rvalue reference.
(T&, Scalar&&)
Combiner | Factory | Inheritable |
---|---|---|
ArithmeticScalar<Op, T> |
||
ScalarMultiply<Scalar, T> |
scalarMultiply |
scalarMultiply<Scalar, T>::operator*= |
ScalarDivide<Scalar, T> |
scalarDivide |
scalarDivide<Scalar, T>::operator/= |
There is no functor provided for these combiners. Open an issue, if you need it.
The combiner's constructor, factory functions and functor take one
reference of the object that will contain the result of copying the
second argument passed by const
reference:
(T&, const T&)
Combiner | Factory | MACROS |
---|---|---|
Copy<T> |
copy |
ENHANCE_COPY_CONSTRUCTOR(QUALIFIER, T) ENHANCE_COPY_ASSIGMENT(QUALIFIER, T) |
Assignment operators and constructors cannot be inherited in a way hat
overwrites the implicit default implementations, so they have to be
given explicitely. This can be done using the ENHANCE_COPY_ASSIGMENT
and ENHANCE_COPY_CONSTRUCTOR
Macros, whose first argument can either
be empty, 'const', or any other valid qualifier.
struct CA {
int i, j;
CA(int i, int j): i(i), j(j) {}
template<class C> void enhance(C& c) const{
c(&CA::i);
}
ENHANCE_COPY_ASSIGMENT(,CA)
ENHANCE_COPY_CONSTRUCTOR(,CA)
};
...
CA p1(4, 12), p2(p1), p3 = p2;
The combiner's constructor, factory functions and functor take one const
reference of the object that will be hashed:
(const T&)
Combiner | Factory | MACROS |
---|---|---|
Hash<T, Hasher = std::hash, HashCombiner = DefaultHashCombiner> |
hash |
ENHANCE_STD_HASH(T) |
To specialize std::hash
for a given type T
, place
ENHANCE_STD_HASH(T)
outside of any namespace.
enhance::DefaultHashCombiner
is taken from
CityHash's Hash128to64
.
Example:
struct Person : EqualComparable<Person> {
string name;
uint age;
Person(string name, uint age): name(name), age(age) {}
template<class C> void enhance(C& c) const{
c(&Person::name, &Person::age);
}
};
ENHANCE_STD_HASH(Person)
TEST_CASE( "hash" ) {
Person p1{"Frank", 21}, p2{"Frank", 21}, p3{"Frank", 22};
std::unordered_set<Person> mt2{p1, p2, p3};
REQUIRE( mt2.size() == 2);
REQUIRE( hash(p1) == hash(p2) );
REQUIRE( hash(p1) == std::hash<Person>()(p2) );
REQUIRE( hash(p1) != hash(p3) );
}
This module adopts the conventions from the
Boost.Serialization
library. The Serializable
class defines a serialize
function that
when passed a saving (loading) archive ar
applies ar <<
(or
ar >>
) iteratively to all accessors.
The combiner's constructor and factory functions take one reference of the object that will be serialized (loaded/saved) and a reference of the corresponding archive:
(T&, Archive&)
Combiner | Factory | MACROS |
---|---|---|
Serialize<Archive, T> |
serialize |
Serializable<T>::serialize(Archive, uint) |
Load<Archive, T> |
||
Save<Archive, T> |
Example:
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
struct P : Serializable<P>, EqualComparable<P> {
int i, j;
P(int i,int j):i(i),j(j){}
template<class C>
void enhance(C& t) const{
t(&P::i, &P::j);
}
};
P p(123, 789), q(2, 45);
boost::archive::text_oarchive oa(ss);
oa << p;
boost::archive::text_iarchive ia(ss);
ia >> q;
REQUIRE(p == q);
The combiner's constructor and factory functions take one const
reference of the object that will be inserted and a reference of the
corresponding output stream:
(const T&, std::ostream&)
Combiner | Factory | Inheritable |
---|---|---|
Insertion<d1,s1,s2,d2, Group, T> |
insertion |
Insertable<d1,s1,s2,d2, T, Group>:: operator<<(std::ostream&, const T&) |
The result will be enclosed in d1
and d2
and the accessors'
results separated by s1s2
, all of which are of type char
.
bool Group
specifies, whether range
accessors are
themselves enclosed in d1
and d2
.
For code examples search this document for the use of Insertable
.
Sometimes, it is easier to simply get a string with the following helper function:
template<class X>
string toString(X&& x){
std::ostringstream s;
s << std::forward<X>(x);
return s.str();
}