Hello all. My name is Germán Diago and I'm been implementing a library
for runtime concepts that I would like
to submit to boost someday. It's a library that is targeting a way to
be able to play well with boost.concept_check library,
complementing it with the ability to hold those concepts in a runtime fashion.
The target of the library is:
-100% nonintrusive:
-Being able to specify that a class models a concept without
touching the class (via traits, not done yet).
-Adapt via adl names of functions which don't match a concept
for a class to be able to use that class as a concept.
-Feel natural: being able as much as possible to convert from concept
hierarchies as it's done for base and derived types.
-Playing nicely with boost.concept_check.
-Being able to specify that a class models more than one unrelated
concept (via mpl::vector maybe).
The features for the library, for now, just target movable and
noncopyable objects, but I'll make it more general
over time. I would like to hear feedback from all of you.
Currently, the library can specify a runtime type that can hold
objects modeling a concept.
It's better to explain the workings of the library with an example, so
here it goes:
//Compile-time concepts
namespace Concepts {
template <class T>
std::size_t getCapacityInMB(const T & t) {
return t.getCapacityInMB();
}
template <class T>
void onOpen(T & t) {
t.onOpen();
}
template <class T>
void onRemove(T & t) {
t.onRemove();
}
template <class T>
struct Device {
BOOST_CONCEPT_USAGE(Device) {
t.getCapacityInMB();
}
T t;
static std::size_t getCapacityInMB(const T & v) {
using Concepts::getCapacityInMB;
return getCapacityInMB(v);
}
};
template <class T>
struct PlugableDevice : Device<T> {
BOOST_CONCEPT_USAGE(PlugableDevice) {
plug_dev.onOpen();
plug_dev.onRemove();
}
T plug_dev;
static void onRemove(T & t) {
using Concepts::onRemove;
onRemove(t);
}
static void onOpen(T & t) {
using Concepts::onOpen;
onOpen(t);
}
};
}
First, I must explain my runtime concept implementation as of now.
We have a model and an interface class for each runtime concept we
want to model. A concept is a class that
is templated like this:
template <class ConceptInterface, template <class> class Model, class
BaseConcept = EmptyType, template <class> class ConceptCheckingClass =
EmptyConcept>
When we template this class with the ConceptInterface and with the
model class, we can hold instances modeling that concept.
Besides that, we can (optionally) enforce the compilance of our types
with the compile-time concepts if we provide the concept checking
class in
the last template parameter.
The template parameter BaseConcept specifies wether the concept
refines another concept. This way it plays nicely with implicit
conversions from
derived-to-base. If a class inherits a concept, that concept will be
inherited by the typedef. The storage for the class is held in the
root of the hierarchy,
and it always holds models for the most derived concept that a class
models (though you have to tell the compiler which Concepts a class
models)
These are two classes covering two concepts (compile-time). We want to be able
to hold unrelated instances modeling those concepts at runtime, so we
can do something
like this for both instances.
//Runtime Concepts layer
class IDeviceC {
public:
virtual std::size_t getCapacityInMB() const = 0;
};
template <class T>
class DeviceCModel : public MovableModelBase<T>, public IDeviceC {
public:
IMPLEMENT_MODEL_DEFAULT_MEMBERS(DeviceCModel)
virtual std::size_t getCapacityInMB() const {
return Concepts::Device<T>::getCapacityInMB(this->getData());
}
};
typedef MovableConceptBase<IDeviceC, DeviceCModel, EmptyType,
Concepts::Device> DeviceC;
//Refined concept
class IPlugableDeviceC : public IDeviceC {
public:
virtual void onOpen() = 0;
virtual void onRemove() = 0;
};
template <class T>
class PlugableDeviceCModel : public MovableModelBase<T>, public
IPlugableDeviceC {
public:
IMPLEMENT_MODEL_DEFAULT_MEMBERS(PlugableDeviceCModel)
virtual void onOpen() {
Concepts::PlugableDevice<T>::onOpen(this->getData());
}
virtual std::size_t getCapacityInMB() const {
Concepts::PlugableDevice<T>::getCapacityInMB(this->getData());
}
virtual void onRemove() {
Concepts::PlugableDevice<T>::onRemove(this->getData());
}
};
//Create concept class that refines DeviceC, The 3rd parameter is the
concept it refines
typedef MovableConceptBase<IPlugableDeviceC, PlugableDeviceCModel,
DeviceC, Concepts::PlugableDevice> PlugableDeviceC;
Now we can provide a program with classes that model those concepts
and try a little:
class LegacyDevice : NonCopyable {
public:
//This class models DeviceC concept
typedef DeviceC ModelOfType;
std::size_t getCapacityInMB() const {
return 10;
}
};
class IpodDevice : NonCopyable {
public:
//This class models PlugableDeviceC concept
typedef PlugableDeviceC ModelOfType;
void onOpen() {
std::cout << "Opening Ipod device" << std::endl;
}
void onRemove() {}
std::size_t getCapacityInMB() const {
return 32 * 1024;
}
};
int main(int argc, char * argv[]) {
std::vector<DeviceC> devices;
IpodDevice ipod;
LegacyDevice legacy;
//devices.push_back(move(legacy_device));
devices.push_back(move(ipod));
devices.push_back(move(legacy));
DeviceC & dev_ref = devices[0];
std::cout << dev_ref->getCapacityInMB() << std::endl;
for (auto & device : devices) {
//device->onOpen();
std::cout << device->getCapacityInMB() << std::endl;
}
//TODO: Doesn't work downcasting because of the lvalue reference, buf
dev_ref is actually holding a model that inherits the most-derived
//PlugableDeviceC & pd = dynamic_cast<PlugableDeviceC&>(dev_ref);
//pd->onOpen();
std::shared_ptr<DeviceC> dev_sptr(new PlugableDeviceC(IpodDevice()));
std::cout << "Shared pointers:\n";
std::cout << (*dev_sptr)->getCapacityInMB() << std::endl;
auto derived_ptr = dynamic_pointer_cast<PlugableDeviceC>(dev_sptr);
(*derived_ptr)->onOpen();
IpodDevice & ipod_device = static_cast<IpodDevice&>(*derived_ptr);
std::cout << "Ipod device back to concrete type: " <<
ipod_device.getCapacityInMB() << std::endl;
std::cout << "Size of DeviceC " << sizeof(DeviceC) << std::endl;
std::cout << "Size of PlugableDeviceC " << sizeof(PlugableDeviceC) <<
std::endl;
std::cout << "The end" << std::endl;
}
You can see that conversions work, except for downcasting. The
downcasting does not work because of
an lvalue-ness problem, but if you instantiate a class like this:
IpodDevice dev;
DeviceC device(move(dev));
It will be holding an object modeling the PlugableDevice concept. The
problem is that I cannot convert
to a PlugableDeviceC because it's being held in a DeviceC, but the
most-derived model is correctly instantiated.
There are still things to do, but I feel they can be accomplished:
-Downcasting:
I can make a special type View<PlugableDeviceC> which can
point to instances held in a DeviceC (maybe, don't know).
-Generalize MovableConceptBase and MovableModelBase classes, so that
you can use classes that are copyable,
equality_comparable, etc.
With these two pieces and maybe some improvements, I think I can
provide a library that plays well with Boost.concept_check library
and which is the runtime counterpart of it. Feedback is very welcome.
for runtime concepts that I would like
to submit to boost someday. It's a library that is targeting a way to
be able to play well with boost.concept_check library,
complementing it with the ability to hold those concepts in a runtime fashion.
The target of the library is:
-100% nonintrusive:
-Being able to specify that a class models a concept without
touching the class (via traits, not done yet).
-Adapt via adl names of functions which don't match a concept
for a class to be able to use that class as a concept.
-Feel natural: being able as much as possible to convert from concept
hierarchies as it's done for base and derived types.
-Playing nicely with boost.concept_check.
-Being able to specify that a class models more than one unrelated
concept (via mpl::vector maybe).
The features for the library, for now, just target movable and
noncopyable objects, but I'll make it more general
over time. I would like to hear feedback from all of you.
Currently, the library can specify a runtime type that can hold
objects modeling a concept.
It's better to explain the workings of the library with an example, so
here it goes:
//Compile-time concepts
namespace Concepts {
template <class T>
std::size_t getCapacityInMB(const T & t) {
return t.getCapacityInMB();
}
template <class T>
void onOpen(T & t) {
t.onOpen();
}
template <class T>
void onRemove(T & t) {
t.onRemove();
}
template <class T>
struct Device {
BOOST_CONCEPT_USAGE(Device) {
t.getCapacityInMB();
}
T t;
static std::size_t getCapacityInMB(const T & v) {
using Concepts::getCapacityInMB;
return getCapacityInMB(v);
}
};
template <class T>
struct PlugableDevice : Device<T> {
BOOST_CONCEPT_USAGE(PlugableDevice) {
plug_dev.onOpen();
plug_dev.onRemove();
}
T plug_dev;
static void onRemove(T & t) {
using Concepts::onRemove;
onRemove(t);
}
static void onOpen(T & t) {
using Concepts::onOpen;
onOpen(t);
}
};
}
First, I must explain my runtime concept implementation as of now.
We have a model and an interface class for each runtime concept we
want to model. A concept is a class that
is templated like this:
template <class ConceptInterface, template <class> class Model, class
BaseConcept = EmptyType, template <class> class ConceptCheckingClass =
EmptyConcept>
When we template this class with the ConceptInterface and with the
model class, we can hold instances modeling that concept.
Besides that, we can (optionally) enforce the compilance of our types
with the compile-time concepts if we provide the concept checking
class in
the last template parameter.
The template parameter BaseConcept specifies wether the concept
refines another concept. This way it plays nicely with implicit
conversions from
derived-to-base. If a class inherits a concept, that concept will be
inherited by the typedef. The storage for the class is held in the
root of the hierarchy,
and it always holds models for the most derived concept that a class
models (though you have to tell the compiler which Concepts a class
models)
These are two classes covering two concepts (compile-time). We want to be able
to hold unrelated instances modeling those concepts at runtime, so we
can do something
like this for both instances.
//Runtime Concepts layer
class IDeviceC {
public:
virtual std::size_t getCapacityInMB() const = 0;
};
template <class T>
class DeviceCModel : public MovableModelBase<T>, public IDeviceC {
public:
IMPLEMENT_MODEL_DEFAULT_MEMBERS(DeviceCModel)
virtual std::size_t getCapacityInMB() const {
return Concepts::Device<T>::getCapacityInMB(this->getData());
}
};
typedef MovableConceptBase<IDeviceC, DeviceCModel, EmptyType,
Concepts::Device> DeviceC;
//Refined concept
class IPlugableDeviceC : public IDeviceC {
public:
virtual void onOpen() = 0;
virtual void onRemove() = 0;
};
template <class T>
class PlugableDeviceCModel : public MovableModelBase<T>, public
IPlugableDeviceC {
public:
IMPLEMENT_MODEL_DEFAULT_MEMBERS(PlugableDeviceCModel)
virtual void onOpen() {
Concepts::PlugableDevice<T>::onOpen(this->getData());
}
virtual std::size_t getCapacityInMB() const {
Concepts::PlugableDevice<T>::getCapacityInMB(this->getData());
}
virtual void onRemove() {
Concepts::PlugableDevice<T>::onRemove(this->getData());
}
};
//Create concept class that refines DeviceC, The 3rd parameter is the
concept it refines
typedef MovableConceptBase<IPlugableDeviceC, PlugableDeviceCModel,
DeviceC, Concepts::PlugableDevice> PlugableDeviceC;
Now we can provide a program with classes that model those concepts
and try a little:
class LegacyDevice : NonCopyable {
public:
//This class models DeviceC concept
typedef DeviceC ModelOfType;
std::size_t getCapacityInMB() const {
return 10;
}
};
class IpodDevice : NonCopyable {
public:
//This class models PlugableDeviceC concept
typedef PlugableDeviceC ModelOfType;
void onOpen() {
std::cout << "Opening Ipod device" << std::endl;
}
void onRemove() {}
std::size_t getCapacityInMB() const {
return 32 * 1024;
}
};
int main(int argc, char * argv[]) {
std::vector<DeviceC> devices;
IpodDevice ipod;
LegacyDevice legacy;
//devices.push_back(move(legacy_device));
devices.push_back(move(ipod));
devices.push_back(move(legacy));
DeviceC & dev_ref = devices[0];
std::cout << dev_ref->getCapacityInMB() << std::endl;
for (auto & device : devices) {
//device->onOpen();
std::cout << device->getCapacityInMB() << std::endl;
}
//TODO: Doesn't work downcasting because of the lvalue reference, buf
dev_ref is actually holding a model that inherits the most-derived
//PlugableDeviceC & pd = dynamic_cast<PlugableDeviceC&>(dev_ref);
//pd->onOpen();
std::shared_ptr<DeviceC> dev_sptr(new PlugableDeviceC(IpodDevice()));
std::cout << "Shared pointers:\n";
std::cout << (*dev_sptr)->getCapacityInMB() << std::endl;
auto derived_ptr = dynamic_pointer_cast<PlugableDeviceC>(dev_sptr);
(*derived_ptr)->onOpen();
IpodDevice & ipod_device = static_cast<IpodDevice&>(*derived_ptr);
std::cout << "Ipod device back to concrete type: " <<
ipod_device.getCapacityInMB() << std::endl;
std::cout << "Size of DeviceC " << sizeof(DeviceC) << std::endl;
std::cout << "Size of PlugableDeviceC " << sizeof(PlugableDeviceC) <<
std::endl;
std::cout << "The end" << std::endl;
}
You can see that conversions work, except for downcasting. The
downcasting does not work because of
an lvalue-ness problem, but if you instantiate a class like this:
IpodDevice dev;
DeviceC device(move(dev));
It will be holding an object modeling the PlugableDevice concept. The
problem is that I cannot convert
to a PlugableDeviceC because it's being held in a DeviceC, but the
most-derived model is correctly instantiated.
There are still things to do, but I feel they can be accomplished:
-Downcasting:
I can make a special type View<PlugableDeviceC> which can
point to instances held in a DeviceC (maybe, don't know).
-Generalize MovableConceptBase and MovableModelBase classes, so that
you can use classes that are copyable,
equality_comparable, etc.
With these two pieces and maybe some improvements, I think I can
provide a library that plays well with Boost.concept_check library
and which is the runtime counterpart of it. Feedback is very welcome.