About the new C++0x concepts. Concepts as types. Containers.
Hello. I've been thinking about generic and object-oriented
programming in C++ and I think that it could be discussed, now that
we're going to have concepts, a way to put heterogeneus concept-based
containers in the language. The proposal would be based in that of
adobe, which tries to make every object Regular and it has many
benefits. I'll explain what we could reach with this approach and why
it fits in a language which has value semantics.
I'm not sure if a class must be movable to be regular, anyway, this
Regular requirement in my definition is defaultconstructible, movable,
copyconstructible and equalitycomparable.
In C++0x, you can write a concept such as this:
concept Drawable<typename T>
requires std::Regular<T> {
void draw();
};
So now, every class that has a draw function can be a Drawable. This
is good. But a problem remains. The problem is that when we want to
instantiate a container with Drawables, and drawables are from
different types, we must inherit from a concrete base class and access
the draw function through virtual calls, and our types are not so
reusable, because your time must inherit the base class type. This is
ok if you're writing code for a framework, for example. But it limits
true code reuse. You have to use containers of pointers. And you loose
partially what concepts promised: being able not to depend on class
hierarchies.
I think it could be a good idea to be able to do something like this
(based on adobe's work on run-time concepts)
///Meets drawable. Needs no inheritance.
class Square;
///Meets drawable. Needs no inheritance
class Rectangle;
//C++0x syntax.
std::vector<Drawable> drawables = { Rectangle(), Square() };
The Drawable would store the Rectangle or whatever in a boost::any-
like structure inside it.
The Drawable run-time concept would be a type which would hold
Rectangles, Squares or whatever you wanted to concept_map from your
old code. It should contain something like boost::any to hold the real
type of the variable You could even access dll code.
The Drawable would forward draw calls to the actual function call.
This way of programming has several advantages:
In case you do this (I know it's a little artificial, anyway, it's an
example):
class Rectangle {
public:
void draw();
};
class Square : public Rectangle {
public:
void draw();
}
If you had this:
std::vector<Drawable> drawables = { Rectangle(), Square() };
///Use of reference
for (Drawable & d : drawables)
d.draw(); ///Calls the correct draw() function.
You can also use any STL algorithm, it'll just work fine without error-
prone approaches.
for_each(drawables.begin(); drawables.end();
std::mem_fun(&Drawable::draw());
And: you needn't to think about predeclaring a function virtual or
not: if the container is a concept-based container, it calls the
correct function without having to worry about. But you can do what
you did before:
std::vector<Rectangle> rectangles;
rectangles[0].draw(); ///This can be inlined
So you get containers as type-checked as you want.
Here you have the best thing that adobe guys were able to do in
current C++. It's an example I did based in its code.
///**********************File
regular.hpp********************************************///
#include <typeinfo>
#include <algorithm>
//En esta clase se acopla la interfaz que queremos para el "run-time
concept"
//Esta clase tendr?? las funciones del run-time concept + las de
sem??ntica regular
template <typename I> // I is a pure virtual interface
struct regular_interface : I
{
typedef regular_interface interface_type;
virtual bool equals(const regular_interface&) const = 0;
virtual regular_interface* clone() const = 0;
virtual const std::type_info& type() const = 0;
virtual ~regular_interface();
};
template <typename I> regular_interface<I>::~regular_interface() { }
template <typename F> // F is an instance of regular_interface
struct regular_instance : F {
typedef typename F::value_type value_type;
typedef typename F::interface_type interface_type;
regular_instance(const value_type & x): F(x) { }
bool equals(const interface_type& x) const {
return (x.type() == typeid(value_type))
&& (static_cast<const regular_instance&>(x).value == this-
value);
}
interface_type* clone() const { return new regular_instance(this-
value); }
const std::type_info& type() const { return typeid(value_type); }
};
template <typename I, // I is a pure virtual interface class
template<class> class D > // D is instance template
class regular_object {
typedef regular_interface<I> interface_type;
interface_type* interface_m;
public:
template <typename T> explicit regular_object(const T& x) :
interface_m(new regular_instance<D<T> >(x)) { }
regular_object(const regular_object& x) :
interface_m(x.interface_m->clone()) { }
regular_object& operator=(const regular_object& x) {
interface_type* tmp = x.interface_m->clone();
std::swap(tmp, interface_m);
delete tmp;
return *this;
}
~regular_object() { delete interface_m; }
const interface_type* operator->() const { return interface_m; }
interface_type* operator->() { return interface_m; }
friend inline
bool operator==(const regular_object& x, const regular_object& y)
{ return x.interface_m->equals(*y.interface_m); }
};
///
************************************************************************///
///File shapeexample.cpp
#include "regular.hpp"
#include <utility>
#include <iostream>
#include <vector>
typedef std::pair<int, int> point;
class shape {
struct drawable_interface {
virtual void draw(const point&) const = 0;
};
template <typename T> // T models Drawable
struct drawable_instance : regular_interface<drawable_interface> {
typedef T value_type;
value_type value; // ??? circle or rectangle goes here
drawable_instance(const value_type& x) : value(x) { }
void draw(const point& where) const { value.draw(where); }
};
point center_m;
regular_object<drawable_interface, drawable_instance> object_m;
public:
template <typename T> // T models Drawable
shape(const point& center, const T& s) : center_m(center),
object_m(s) { }
void draw() const { object_m->draw(center_m); }
point where() const { return center_m; }
void move(const point& to) { center_m = to; }
friend bool operator==(const shape & x, const shape & y);
};
inline bool operator==(const shape& x, const shape& y)
{ return (x.center_m == y.center_m) && (x.object_m == y.object_m); }
struct circle {
int radius;
circle(int r) : radius(r) { }
void draw(const point&) const {
std::cout << "Dibujo c??rculo" << std::endl;
}
friend bool operator == (const circle& x, const circle& y)
{ return x.radius == y.radius; }
};
struct rectangle {
int width, height;
rectangle(int w, int h) : width(w), height(h) { }
void draw(const point&) const {
std::cout << "Dibujo rect??ngulo" << std::endl;
}
friend inline bool operator == (const rectangle& x, const rectangle&
y)
{ return x.width == y.width && x.height == y.height; }
};
struct cuadrado : rectangle {
public:
explicit cuadrado(int lado = 5) : rectangle(lado, lado) {}
void draw(const point &) const { std::cout << "Dibujo cuadrado" <<
std::endl; }
friend bool operator==(const cuadrado & x, const cuadrado & y)
{ return x.width == y.width &&
x.height == y.height;
}
};
int main(int argc, char * argv[])
{
std::vector<shape> s1;
s1.push_back(shape(point(1, 2), circle(3)));
s1.push_back(shape(point(4, 5), circle(6)));
s1.push_back(shape(point(7, 8), rectangle(9, 10)));
s1.push_back(shape(point(0, 0), cuadrado()));
std::vector<shape> s2(s1);
reverse(s1.begin(), s1.end());
find(s1.begin(), s1.end(), shape(point(4, 5), circle(6)))-
move(point(10, 20));
for_each(s1.begin(), s1.end(), std::mem_fun_ref(&shape::draw));
for_each(s2.begin(), s2.end(), std::mem_fun_ref(&shape::draw));
}
And the reference to runtime concepts from adobe:
http://opensource.adobe.com/wiki/index.php/Runtime_Concepts
Bye. And thanks for your time.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]