Boost logo

Boost :

From: Mat Marcus (mat-lists_at_[hidden])
Date: 2007-11-02 17:19:35


On Oct 20, 2007 1:15 PM, Corrado Zoccolo <czoccolo_at_[hidden]> wrote:
> Hi Simon
>
>
> On 10/20/07, Simon Francis <thejello_at_[hidden]> wrote:
> >
> > http://opensource.adobe.com/classadobe_1_1poly.html
> >
> > I already knew adobe poly, when I started writing optional_poly. The aim
> is similar, but the approach is completely different.
> With Adobe poly, you have to design the interfaces, and write "concept maps"
> to specifically to work with it.
>
> Example, from Adobe poly test:
>
> struct poly_measurable_interface : adobe::poly_copyable_interface
> {
> virtual double size() const = 0;
>
> virtual ~poly_measurable_interface() { }
> };
>
> /*************************************************************************************************/
> // This class template implements the abstract measurable interface in terms
> of a type T that
> // Models the MeasurableConcept
>
> template <typename T>
> struct poly_measurable_instance : adobe::optimized_storage_type<T,
> poly_measurable_interface>::type
> {
> ADOBE_CLASS_REQUIRE(T, , MeasurableConcept);
>
> poly_measurable_instance(const T& x)
> : adobe::optimized_storage_type<T,
> poly_measurable_interface>::type(x) {}
>
> poly_measurable_instance(poly_measurable_instance& x, adobe::move_ctor
> m)
> : adobe::optimized_storage_type<T,
> poly_measurable_interface>::type(x, m) {}
>
> double size() const
> {
> return MeasurableConcept<T>::size(this->get());
> }
> };
>
> /*************************************************************************************************/
> // Another boilerplate class, measurable serves as a template parameter to
> the poly<> machinery
> // tying together the interface and implementation above.
>
> struct measurable : adobe::poly_base<poly_measurable_interface,
> poly_measurable_instance>
> {
> // No delegating constructors (yet), so we call base constructor
> manually
> template <typename T>
> explicit measurable(const T& s)
> : adobe::poly_base<poly_measurable_interface,
> poly_measurable_instance>(s) { }
>
> measurable(measurable& x, adobe::move_ctor m)
> : adobe::poly_base<poly_measurable_interface,
> poly_measurable_instance>(x, m) {}
>
> // No forwarding in C++, so we do it manually
> double size() const
> { return interface_ref().size(); }
> };
>
> typedef adobe::poly<measurable> poly_measurable;
>
> Thanks to all this boilerplate code, you can use the usual dot notation to
> access the members of the class.
>
> My approach is different. You don't have to change the interfaces to use
> optional_poly (so it is non intrusive with existing hierarchies), and you
> don't need all this boilerplate code, but you have to use the -> operator to
> access members of the class.

[snip]
--------------

There have been a few posts on the boost lists recently mentioning the
adobe::poly library. Since the poly documentation is still somewhat
incomplete, except for a few research-orineted papers, I thought I'd
give a bit more detail here, to help clarify the differences between
poly<> and possibly similar libraries.

You correctly note that the adobe::poly library requires more
machinery to be provided by certain stakeholers. It does not,
however, intrude into existing concrete classes. To better understand
where the adobe::poly library lives in the design continuum, I'll
consider a possible manner in which a piece of code may over its
lifetime. I'll start with a (toy example of a) class that
automatically sizes and positions widgets from a particular GUI
library inside of a dialog. In this scenario, users of the layout
engine first "append" widgets to the layout collection. Once all the
widgets have been appended, the user can ask the engine to "solve" the
layout. In order to solve the layout, the engine will call a measure
function on each widget to obtain its desired extents and other layout
constraints. When the layout is solved, the engine informs the widgets
of their final location by invoking the place(Widget, extents_t)
member function:

//
// Code evolution: stage 1--
//layout engine operating on aspecific concrete type

class Widget {/*...*/};
// API-specific widget class
class extents_t {/*...*/}; // encapsulates screen object size and
location

struct layout_engine {
  void append(Widget placeable)
  { placeables_m.push_back(placeable); }

  void solve() {
    extents_m.resize(placeables_m.size());
    for(int i = 0; i != placeables_m.size(); ++i)
       measure(placeables_m[i], extents_m[i]);
    // ommitted: code to solve layout constraints
    // and update place_data_m (ommitted)
    for(int i = 0; i != placeables_m.size(); ++i)
       place(placeables_m[i], place_data_m[i]);
  }
  vector<extents_t> extents_m;
  vector<Widget> placeables_m;
};

Version #1 of the engine above is designed to work on a single
specific Widget type. Later in life, this code is to be ported to
other platforms. One approach to generalization is to templatize the
layout engine on the widget type. The code for the layout engine
doesn't need to change.
//
// Code evolution: stage 2 --lifting the engine
// (at compile time) using templates
//

template <class T>
 struct layout_engine {
   void append(T placeable)
   { placeables_m.push_back(placeable); }

   void solve() {
     extents_m.resize(placeables_m.size());
     for(int i = 0; i != placeables_m.size(); ++i)
        measure(placeables_m[i], extents_m[i]);
     // ommitted: code to solve layout constraints
     // and update place_data_m (ommitted)
     for(int i = 0; i != placeables_m.size(); ++i)
        place(placeables_m[i], place_data_m[i]);
   }
   vector<extents_t> extents_m;
   vector<T> placeables_m;
 };

Version #2 of the code (above) assumes that measure and place
operations with the right signatures are implemented for the T widget
type. To improve usability of our class template, we use the
boost::concept_check library to improve error messages in case the
required measure and place operations are not present (not shown
here). In the test code for adobe::poly that you cited above, you can
see some of this machinery (ADOBE_CLASS_REQUIRE). We go even further
simulating the C++ '0x concept_map feature through the use of
operations qualified by MeasurableConcept. But this is not really
poly<>-specific so I won't say more about it now.

For the next stage of our code's evolution, we wish be able to use the
layout engine in contexts where the widget type is not known until
runtime. Since we don't wish to intrude on our concrete widget types
to make them inherit from a base class, the usual object-oriented
abstract base class approach doesn't appeal. Such an approach might
not even be possible, given that we may not control the source code
for the widget classes. One principle applied in the design of the
poly<> library was that the ability for an object to behave
polymorphically is not one that should be introduced/paid for at
design-time. Instead, we support a pay-as-you-go plan at the point of
polymorphic use. The adobe::poly library is a toolkit designed to
facilitate the task of providing non-intrusive value-based wrappers
that allow concrete classes to be used in a runtime polymorphic
manner, without change of interface.

We could retain the previous version of the layout_engine unchanged,
instantiating it on the poly_placeable runtime polymorphic wrapper
when we want runtime polymorphic behavior, and we could instantiate
the layout engine "library" on a concrete type in applications where
the widget type is known at compile time. For the runtime-polymorphic
wrapper case this would simply look like:

// Code evolution: stage 3 -- instantiating the layout_engine class on
// the runtime polymorphic wrapper type poly_placeable

 layout_engine<poly_placeable> e;

As an alternate use case (e.g. for dynamic libraries), we sometimes
emphasize the runtime polymorphic usage by promoting/de-templatizing
the layout_engine class template into a plain (non-templated) class:

 struct layout_engine {
   void append(poly_placeable placeable);
   void solve();
   vector<extents_t> extents_m;
   vector<poly_placeable> placeables_m; };

The current version of the poly library assumes two different kinds of
clients. The first kind of client (the wrapper author) is the provider
of a poly "wrapper", such as the poly_measurable wrapper above. This
client uses the poly library to reduce the burden of producing a
particular non-intrusive polymorphic value type. Such clients must
provide: an abstract base class specifying the required/available
operations, (this is the role of poly_measurable_interface in the
poly_test code), a class template that implementing the abstract
interface in terms of concrete types that provide offer the
appropriate signature (this is the role of poly_measurable_instance in
the poly_test example), and a small glue class that holds the previous
two artifacts together (measureable in the adobe_test example). Once
those three artifacts have been written, the poly<> wrapper can be
instantiated:

 typedef adobe::poly<measurable> poly_measurable;
// in the poly_test example

Poly offers a number of other features (small object optimization,
move semantics using the NRVO-based adobe::move library, etc.) but I
won't discuss those further. At this point the responsibilities of the
first kind of poly client, the wrapper author, are complete.

The second kind of client is the end user of classes such as
poly_placeable. In the evolving code example above, we can look
briefly at how the code that calls the layout engine evolves. The
stage 1 calling sequence might look like:

 void place(widget, extents_t);
 exents_t measure(widget, extents_t);
 //...
 layout_engine e; // #1

 void layout_dialog() {
   Widget widget1, widget2;
 //append some widgets:
   e.append(widget1); //#2
   e.append(widgetn); //#3
 //...
   e.solve();
 }

At stage 2, the only line that changes is #1:

 layout_engine<Widget> e; //#1'

At stage 3, we need to change //#1 once more to

 layout_engine<poly_placeable> e; //#1''

In the conceptC++ version of poly, or in a version in which
poly_constructors were non-explicit, no further changes would be
required. In the current C++ '03 version, poly<> has an explicit
constructor, so we also change lines #2 and #3 to:

   e.append(poly_placeable(widget1)); //#2''
   e.append(poly_placeable(widgetn)); //#3''

I haven't touched on other adobe::poly features such as the
assignability across different poly<> types, dynamic dispatch and
dynamic_cast that poly can provide, but I've probably already said
more than anyone wanted to hear about it in this thread.

More info can be found at the following locations:

http://opensource.adobe.com/
For the adobe source library (including poly)

http://www.emarcus.org/papers/MPOOL2007-marcus.pdf
http://www.emarcus.org/papers/MPOOL2007-slides-marcus.pdf

# Runtime Polymorphic Generic Programming--Mixing Objects and Concepts
in ConceptC++
Mat Marcus, Adobe Systems, Inc., Jaakko Järvi, Texas A&M University,
Sean Parent, Adobe Systems, Inc.
From: The 6th International Workshop on Multiparadigm Programming with
Object-Oriented Languages at the ECOOP 2007.

and a little bit here:

http://www.speakeasy.org/~mmarcus/papers/gpce2007f-authors-version.pdf

# Library Composition and Adaptation using C++ Concepts
Jaakko Järvi, Texas A&M University, Mat Marcus, Adobe Systems, Inc.,
Jacob N. Smith, Texas A&M University.
From: Proceedings of the 6th international conference on Generative
programming and component engineering, 2007.
Copyright ACM, 2007. This is the author's version of the work. It is
posted here by permission of ACM for your personal use. Not for
redistribution. The definitive version was published in the
Proceedings of the 6th International Conference on Generative
Programming and Component Engineering (Salzburg, Austria, October 01 -
03, 2007). GPCE '07 http://doi.acm.org/10.1145/1289971.1289984

 - Mat


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk