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 template-based C++ Standard Library was designed for maximum efficiency and flexibility. Boost and other popular C++ libraries followed the paradigm. The consequence of the design is that one aspect falls short: communication between binary safe components via data structures defined by such libraries. The ability to exchange instances of types or invoke functions which take or return objects across component boundaries is limited to libraries that use compatible (or identical) compiler and linker settings. In many scenarios you are forced to write pure generic code and deliver it as such, where you'd rather hide the implementation. Think of:
  • abstracting away the kind of used container or iterator
  • forward a complex range created by piping sequence of Boost.Range or
  • expose a Boost.Signal in a public API etc.

In general, there are issues with regard to binary compatibility that make it hard to accomplish following things across component boundaries:
  • pass instances of objects
  • invoke functions or member functions of objects
  • allocate and free heap 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) - vc110 | Release | dynamic run-time etc.

In this context a binary component is a library or program which provides certain services advertised through a set of contracts and promises to be binary compatible or put simply - it provides a public Application Binary Interface (ABI).

Vex defines a set of contracts instances of which can be safely exchanged across component boundaries. 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 memory management facilities.

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.Iterable: 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.Iterable 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::iterable. The library names correspond to the folders inside the top level vex folder. Inside the library, there are typically two other namespaces:
  1. contract: set of contracts used to represent specific functionality
  2. implementation: Default implementations which conform with the contract specifications.

Most libraries contain further "standardized" units which are given names using the following conventions:
  1. A set of factory functions called make_[kind of the result type]_[kind of the element]. These functions create instances of concrete types which implement a (set of) contract(s) using the types from implementation namespace. For example, make_raw_range creates a raw pointer to a default implementation of contract::range based on the passed parameters. Similarly, make_intrusive_range creates a boost::intrusive_ptr<range> and so on.
  2. A set of smart pointer-like wrappers for instances of various contracts, e.g. a wrapper for any contract::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.Iterable 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.Iterable 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.Iterable adopts (and slightly modifies) the concept of Ranges from D-language Standard Library std.range which are more appropriate for this purpose.

Assume, there is a component foo with binary safe API which needs to consume an iterable range of values. Because of the binary safety requirement, it is not acceptable to pass C++ Standard Library containers or iterator ranges directly. With Vex, the component declares an API which uses a binary safe contract defined by Vex:
// foo/api.h
#include <vex/iterable/contract/range.h>
    
namespace foo { 
    namespace vi = vex::iterable;
    void api(vi::contract::range<vi::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 function which creates a wrapper element, in this example a type erasing RAII safe wrapper with standard smart pointer semantics for ranges:
#include <vex/iterable/make_any_range.h>
#include <foo/api.h>

int main() 
{
    namespace vi = vex::iterable;

    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::iterable::any_range<vex::iterable::random_access_tag, int&>:
    if (auto r = vi::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 a Vex contract declaration:
// pulls in the contract declaration
#include <vex/iterable/contract/range.h>

// pulls in boost::intrusive_ptr and a set of utility function templates
#include <vex/core/as_intrusive.h> 

namespace foo { 
    namespace vi = vex::iterable;
    void api(vi::contract::range<vi::random_access, int&>* p_range)
    {
        // auto resolves to boost::intrusive_ptr<vi::contract::range<vi::random_access, int&>*>
        // which takes care of disposing the passed contract pointer correctly
        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;
            }
        }        
    }
}

Last edited Feb 24 at 8:29 AM by michalik, version 57