I am using this type of pattern in my code to handle traits for various things. Firstly, I have a set of traits templates; these are specialised by an enum value:
template<int>
struct PixelProperties;
/// Properties of UINT8 pixels.
template<>
struct PixelProperties< ::ome::xml::model::enums::PixelType::UINT8> :
public PixelPropertiesBase<PixelProperties< ::ome::xml::model::enums::PixelType::UINT8> >
{
/// Pixel type (standard language type).
typedef uint8_t std_type;
/// Pixel type (big endian).
typedef boost::endian::big_uint8_t big_type;
/// Pixel type (little endian).
typedef boost::endian::little_uint8_t little_type;
/// Pixel type (native endian).
typedef boost::endian::native_uint8_t native_type;
/// This pixel type is not signed.
static const bool is_signed = false;
/// This pixel type is integer.
static const bool is_integer = true;
/// This pixel type is not complex.
static const bool is_complex = false;
};
I then have code which uses the traits. Mostly, it uses them directly, but in some cases it needs to switch on the enum value, for example:
bool
isComplex(::ome::xml::model::enums::PixelType pixeltype)
{
bool is_complex = false;
switch(pixeltype)
{
case ::ome::xml::model::enums::PixelType::INT8:
is_complex = PixelProperties< ::ome::xml::model::enums::PixelType::INT8>::is_complex;
break;
case ::ome::xml::model::enums::PixelType::INT16:
is_complex = PixelProperties< ::ome::xml::model::enums::PixelType::INT16>::is_complex;
break;
[...]
}
return is_complex;
}
This is for run-time rather than compile-time introspection. My problem is that it requires every single enum to be cased in the switch statement, which is a pain to maintain. I now have a situation where I need to handle all the combinations of two sets of enums, and this would require nested switch statements if I was to handle it as above. The combinatorial complexity obviously won't scale, but at the same time I can't see a good way to drive the template expansion for each combination other than explicitly. This is a contrived example of using such combinatorial expansion for run-time unit conversion:
#include <iostream>
#include <boost/units/unit.hpp>
#include <boost/units/make_scaled_unit.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si.hpp>
using boost::units::quantity;
using boost::units::quantity_cast;
using boost::units::make_scaled_unit;
using boost::units::scale;
using boost::units::static_rational;
namespace si = boost::units::si;
enum LengthUnit
{
MILLIMETRE,
MICROMETRE,
NANOMETRE
};
template<int>
struct UnitProperties;
template<>
struct UnitProperties<MILLIMETRE>
{
typedef make_scaled_unit<si::length,scale<10,static_rational< -3> > >::type unit_type;
};
template<>
struct UnitProperties<MICROMETRE>
{
typedef make_scaled_unit<si::length,scale<10,static_rational< -6> > >::type unit_type;
};
template<>
struct UnitProperties<NANOMETRE>
{
typedef make_scaled_unit<si::length,scale<10,static_rational< -9> > >::type unit_type;
};
struct Quantity
{
double value;
LengthUnit unit;
};
template<int SrcUnit, int DestUnit>
double
convert(double value)
{
typedef typename UnitProperties<SrcUnit>::unit_type src_unit_type;
typedef typename UnitProperties<DestUnit>::unit_type dest_unit_type;
quantity<src_unit_type, double> src(quantity<src_unit_type, double>::from_value(value));
quantity<dest_unit_type, double> dest(src);
return quantity_cast<double>(dest);
}
Quantity
convert(Quantity q, LengthUnit newunit)
{
switch(q.unit)
{
case MILLIMETRE:
switch(newunit)
{
case MILLIMETRE:
return Quantity({convert<MILLIMETRE, MILLIMETRE>(q.value), MILLIMETRE});
break;
case MICROMETRE:
return Quantity({convert<MILLIMETRE, MICROMETRE>(q.value), MICROMETRE});
break;
case NANOMETRE:
return Quantity({convert<MILLIMETRE, NANOMETRE>(q.value), NANOMETRE});
break;
}
break;
case MICROMETRE:
switch(newunit)
{
case MILLIMETRE:
return Quantity({convert<MICROMETRE, MILLIMETRE>(q.value), MILLIMETRE});
break;
case MICROMETRE:
return Quantity({convert<MICROMETRE, MICROMETRE>(q.value), MICROMETRE});
break;
case NANOMETRE:
return Quantity({convert<MICROMETRE, NANOMETRE>(q.value), NANOMETRE});
break;
}
break;
case NANOMETRE:
switch(newunit)
{
case MILLIMETRE:
return Quantity({convert<NANOMETRE, MILLIMETRE>(q.value), MILLIMETRE});
break;
case MICROMETRE:
return Quantity({convert<NANOMETRE, MICROMETRE>(q.value), MICROMETRE});
break;
case NANOMETRE:
return Quantity({convert<NANOMETRE, NANOMETRE>(q.value), NANOMETRE});
break;
}
break;
}
}
int main()
{
Quantity q { 34.5, MICROMETRE };
auto r = convert(q, NANOMETRE);
std::cout << q.value << " micrometres is " << r.value << " nanometres\n";
}
In other situations, I'm using boost::variant and its static_visitor to drive expansion of all the combinations. This works well, but it might not work here--the types for different traits may be the same but have different behaviour. Unless it's possible to encode the enum values in the variant type?
Or would the Boost Preprocessor macros or C++11 variadic templates be able to provide a better solution here? Edit: Or maybe boost::mpl::foreach?
Thanks for any suggestions, Roger