Project Description
Vex is a collection of lightweight, extensible and easy to use C++ components intended to solve a couple of vexing problems inherent to ANSI C++.
Rationale
The C++ Standard Library was designed for maximum efficiency and flexibility. Boost and other popular C++ libraries followed this paradigm. The consequence of the design is that one aspect falls short: Component-based design and the ability to encapsulate logic into binary safe components. There are issues with regard to binary compatibility that make it hard to accomplish following things across component boundaries:
- safely pass instances of objects
- call member functions of objects
- allocate and deallocate memory
- using run-time type information (RTTI)
- throwing and catching exceptions
Vex is intended to provide a universal framework to simplify component based design based on pure ANSI C++. In this context, binary compatibility means one platform - different tool sets | configurations | run-time libraries, for example: Windows (x86) - vc10 | Release | dynamic run-time etc. A
binary component is a library or program which provides certain services advertised through a set of contracts and promises to be binary compatible.
Vex defines a set of contracts which can be safely exchanged across component boundaries compiled for one platform, but with different tool-sets or linked against different run-times. A contract is defined as something which (almost) all platforms agree on as being binary compatible across different tool-sets or tool-set settings: a pure abstract class with custom deallocation facility.
Project status
Vex is a work-in-progress project. For current state and associated issues, please refer to
vex.devkit. Currently, following libraries are scheduled for the first release:
- Vex.Range: Expose C++ Standard Library conforming ranges and containers as binary safe contracts
- Vex.Functional: Exposes callable types (signals, delegates and events) as binary safe contracts
- Vex.Query: Implements Linq-like queries and manipulators for types conforming to Vex.Range concepts
Any kind of cooperation, feedback or criticism are welcome...
Project structure
Each
Vex library is embedded in a namespace
vex::[library name], e.g.
vex::range. The library names correspond to the folders inside the top level
vex folder. Inside the library, there are typically two other namespaces:
- contract: set of contracts used to represent a specific functionality
- implementation: Default implementations which conform with the contract specifications.
Most libraries contain further "standardized" units which are given names using the following conventions:
- A set of factory functions called make_[kind of the result type]_[kind of the element]. These functions create instances of concrete types which conform to a given contract using the types from implementation namespace. For example, make_raw_range creates a raw pointer to a default implementation of abstract_range based on the passed parameters. Similarly, make_intrusive_range creates a boost::intrusive_ptr<abstract_range> and so on.
- A set of smart pointer-like wrappers for instances of various contracts, e.g. a wrapper for any abstract_range contract is called any_range etc. There are also factory functions to create instances of any_ wrappers called make_any_, for example: make_any_range, make_any_delegate etc.
In order to build and test the library (which is currently header only) you'll need to check out the
vex SDK which contains build scripts, dependencies and tests.
Motivating Example
Vex.Range library defines contracts for various types of iterable ranges and sequences and a set of factory functions to create default implementations from instances of types conforming to C++ Standard Library concepts of a Range or Container. The Vex.Range types are not conforming to C++ Standard library container or range concepts - it is terribly awkward to create an abstract model for STL iterator pairs and containers. Instead Vex.Range adopts (and slightly modifies) the concept of Ranges from D-language Standard Library (see std.range) which are more appropriate for this purpose.
Assume, there is a component foo with binary safe API which needs to consume a iterable of values. Because of the binary safety requirement, passing the C++ Standard Library collections directly is not acceptable. With Vex, the component declares an API which uses a binary safe contract defined by Vex.
// foo/api.h
#include <vex/range/contract/abstract_range.h>
namespace foo {
void api(vex::range::contract::abstract_range<vex::range::random_access, int&>* p_range);
}
The client uses C++ Standard Library or Boost data types as usual. When it needs to pass an instance of a Standard Library container or a conforming Boost type to foo it creates a default implementation of the contract using a factory element which creates a wrapper element, in this example a type erasing, RAII safe wrapper with standard smart pointer semantics for ranges:
#include <vex/range/make_any_range.h>
#include <foo/api.h>
int main()
{
namespace vr = vex::range;
std::vector<int> t_source = {
0, 1, 2, 3, 4, 5, 6
};
// Create a std::vector wrapper and pass an instance to foo_component::api.
// 'auto' resolves to vex::range::any_range<vex::range::random_access_tag, int&>:
if (auto r = vr::make_any_range(t_source)) {
// 'as_raw' is a forwarding function template which takes care of
// vex reference counting conventions
foo::api(vex::as_raw(r));
}
}
On foo's side, there is nothing known about the implementation details, or the used Standard Library container, you only include the Vex contract declaration:
// pulls in the contract declaration
#include <vex/range/contract/abstract_range.h>
// pulls in boost::intrusive_ptr and a set of utility function templates
#include <vex/core/as_intrusive.h>
namespace foo {
void api(vex::range::contract::abstract_range<vex::range::random_access, int&>* p_range)
{
if (auto r = vex::as_intrusive(p_range)) {
while (r->move_front()) {
// access the front of the range:
auto v = r->front();
// modify the content of current front:
r->front() = v + 1;
}
}
}
}