Lumiera  0.pre.03
»edit your freedom«
polymorphic-value.hpp File Reference

Go to the source code of this file.

Description

A mechanism to allow for opaque polymorphic value objects.

This template helps to overcome a problem occasionally encountered in C++ programming, based on the fundamental design of C++, which favours explicit low-level control, copying of values and strict ctor-dtor pairs. Many object oriented design patterns build on polymorphism, where the actual type of an object isn't disclosed and collaborations rely on common interfaces. This doesn't mix well with the emphasis the C/C++ language puts on efficient handling of small data elements as values and explicit control of the used storage; indeed several of the modern object oriented and functional programming techniques more or less assume the presence of a garbage collector or similar mechanism, so 'objects' need just to be mentioned by reference.

In C++, in order to employ many of the well known techniques, we're bound more or less to explicitly put the objects somewhere in heap allocated memory and then pass an interface pointer or reference into the actual algorithm. Sometimes, this hinders a design based on constant values and small descriptor objects used inline, thus forcing into unnecessarily complex and heavyweight alternatives. While it's certainly pointless to fight the fundamental nature of the programming language, we may try to pull some (template based) trickery to make polymorphic objects fit better with the handling of small copyable value objects. Especially, C++ gives a special meaning to passing parameters as const& – typically constructing an anonymous temporary object conveniently just for passing an abstraction barrier (while the optimiser can be expected to remove this barrier and the accompanying nominal copy operations altogether in the generated code). Consequently the ability to return a polymorphic object from a factory or configuration function by value would open a lot of straight forward design possibilities and concise formulations.

how to build a copyable value without knowing it's layout in detail

So the goal is to build a copyable and assignable type with value semantics, without disclosing the actual implementation and object layout at the usage site. This seemingly contradictory goal can be achieved, provided that

  • the space occupied by the actual implementation object is limited, so it can be placed as binary data into an otherwise opaque holder buffer
  • and the actual implementation object assists with copying and cloning itself, observing the actual implementation data layout

The PolymorphicValue template implements this idea, by exposing a copyable container with value semantics to the client code. On instantiation, a common base interface for the actual value objects needs to be provided; the resulting instance will be automatically convertible to this interface. Obviously this common interface must be an ABC or at least contain some virtual functions. Moreover, the PolymorphicValue container provides static builder functions, allowing to place a concrete instance of a subclass into the content buffer. After construction, the actual type of this instance will be forgotten ("type erasure"), but because of the embedded vtable, on access, the proper implementation functions will be invoked.

Expanding on that pattern, the copying and cloning operations of the whole container can be implemented by forwarding to appropriate virtual functions on the embedded payload (implementation) object – the concrete implementation of these virtual functions can be assumed to know the real type and thus be able to invoke the correct copy ctor or assignment operator. For this to work, the interface needs to expose those copy and clone operations somehow as virtual functions. There are two alternatives to fulfil this requirement:

  • in the general case, the common base interface doesn't expose such operations. Thus we need to mix in an additional management interface; this can be done by subclassing the desired implementation type, because this concrete type is irrelevant after finishing the placement constructor. In order to re-access this management interface, so to be able to invoke the copy or clone operations, we need to do an elaborate re-cast operation, first going down to the leaf type and then back up into the mixed-in management interface. Basically this operation is performed by using a dynamic_cast<CopyAPI&>(bufferContents)
  • but when the used client types provide some collaboration and implement this management interface either directly on the API or as an immediate sub-interface, then this copy/management interface is located within the direct inheritance chain and can be reached by a simple static_cast. Indeed, as we're just using a different meaning of the VTable, only a single indirection (virtual function call) is required at runtime in this case to invoke the copy ctor or assignment operator. Thus, in this latter (optimal) case, the fact that PolymorphicValue allows to conceal the actual implementation type comes with zero runtime overhead, compared to direct usage of a family of polymorphic types (with VTable).

So, how can the implementation of copy or assignment know the actual type to be copied? Basically we exploit the fact that the actual instance lives in an opaque buffer within the "outer" container. More specifically, we place it into that buffer – thus we're able to control the actual type used. This way, the actual copy operations reside in an Adapter type, which lives at the absolute leaf end of the inheritance chain. It even inherits from the "implementation type" specified by the client. Thus, within the context of the copy operation, we know all the concrete types.

Todo:
the actual implementation for copy support basically achieves this goal, but it is somewhat confusing and muddled, and not entirely correct in some corner cases (esp. when the target type does not collaborate but also does only support copy construction, but no assignment. In fact, part of the solution implemented here is known as "virtual copy support"; meanwhile we use a generic version of that pattern in our Variant container. Thus, at some point, we should rework this aspect of the solution to make it more orthogonal, clearer to understand and more correct. /////////////////////////////////////////////TICKET #1197

using polymorphic value objects

To start with, we need a situation where polymorphic treatment and type erasure might be applicable. That is, we use a public API, and only that, in any client code, while the concrete implementation is completely self contained. Thus, in the intended use, the concrete implementation objects can be assembled once, typically in a factory, and after that, no further knowledge of the actual implementation type is required. All further use can be coded against the exposed public API.

Given such a situation, it might be desirable to conceal the whereabouts of the implementation completely from the clients employing the generated objects. For example, the actual implementation might rely on a complicated subsystem with many compilation dependencies, and we don't want to expose all those details on the public API.

Now, to employ PolymorphicValue in such a situation, on the usage side (header):

  • expose the public API, but not the implementation type of the objects
  • define an instantiation of PolymorphicValue with this API
  • be sure to define a hard wired size limit not to be exceeded by the actual implementation objects (PolymorphicValue's ctor has an assertion to verify this constraint)
  • provide some kind of factory for the clients to get the actual polymorphic value instances. Clients may then freely move and copy those objects, but do not need to know anything about the actual implementation object layout (it could be figured out using RTTI though)

On the implementation side (separate compilation unit)

  • include the definition of the PolymorphicValue instantiation (of course)
  • define the implementation types to inherit from the public API
  • implement the mentioned factory function, based on the static build PolymorphicValue::build functions, using the actual implementation type as parameter.
See also
polymorphic-value-test.cpp
opaque-holder.hpp other similar opaque inline buffer templates
lib::time::Mutation usage example

Definition in file polymorphic-value.hpp.

#include "lib/error.hpp"
#include "lib/meta/duck-detector.hpp"
#include "lib/util.hpp"

Classes

class  PolymorphicValue< IFA, storage, CPY >::Adapter< IMP >
 Implementation Helper: add support for copy operations. More...
 
class  allow_Clone_but_no_Copy< T >
 helper to detect if the API supports only copy construction, but no assignment More...
 
struct  AssignmentPolicy< API, YES >
 Policy class for invoking the assignment operator. More...
 
struct  AssignmentPolicy< API, enable_if< allow_Clone_but_no_Copy< API > > >
 special case when the embedded payload objects permit copy construction, but no assignment to existing instances. More...
 
class  CloneValueSupport< BA >
 A variation for limited copy support. More...
 
class  CopySupport< IFA, BA >
 Interface for active support of copy operations by the embedded client objects. More...
 
struct  EmptyBase
 
class  exposes_CloneFunction< T >
 helper to detect presence of a function to support clone operations More...
 
class  PolymorphicValue< IFA, storage, CPY >
 Template to build polymorphic value objects. More...
 
struct  Trait< TY, YES >
 trait template to deal with different ways to support copy operations. More...
 
struct  Trait< TY, enable_if< exposes_CloneFunction< TY > > >
 Special case when the embedded types support copying on the API level, e.g. More...
 

Namespaces

 lib
 Implementation namespace for support and library code.