替换在switch语句中扩展模板

时间:2016-02-12 11:00:14

标签: c++ templates boost enums

我在我的代码中使用这种类型的模式来处理各种事物的特征。首先,我有一组特征模板;这些是由枚举值专门化的:

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;
};

然后我有使用特征的代码。大多数情况下,它直接使用它们,但在某些情况下,它需要打开枚举值,例如:

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;
}

这是针对运行时而不是编译时内省。我的问题是它需要在switch语句中装入每个枚举,这很难维护。我现在有一种情况,我需要处理两组枚举的所有组合,如果我要按上述方法处理它,这将需要嵌套的switch语句。组合复杂性显然不会扩展,但同时我看不到一个好的方法来驱动除了显式之外的每个组合的模板扩展。这是一个使用这种组合扩展进行运行时单位转换的人为例子:

#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";
}

在其他情况下,我使用boost :: variant及其static_visitor来驱动所有组合的扩展。这很好用,但它可能在这里不起作用 - 不同特征的类型可能相同但行为不同。除非可以在变体类型中编码枚举值吗?

或者,Boost预处理器宏或C ++ 11可变参数模板能够在这里提供更好的解决方案吗?编辑:或者也许是boost :: mpl :: foreach?

感谢您的任何建议, 罗杰

2 个答案:

答案 0 :(得分:1)

Boost.Preprocessor将允许您隐藏组合复杂性。

要使用它,您可以将类型列表的“主”定义移动到Boost.Preprocessor数据类型中。我喜欢序列,所以我会用一个:

#define TYPES_SUPPORTED (UINT8)(INT8)(UINT16)(INT16)

然后将使用它来生成枚举:

enum class PixelType
{
  BOOST_PP_SEQ_ENUM(TYPES_SUPPORTED)
};

并包装switch声明:

#define ONE_CASE(maR, maProperty, maType) \
  case maType: \
    maProperty = PixelProperties< ::ome::xml::model::enums::PixelType::maType>::maProperty;\
    break;

switch (pixelType)
{
  BOOST_PP_SEQ_FOR_EACH(ONE_CASE, is_complex, TYPES_SUPPORTED)
}

#undef ONE_CASE

或者包装“二次”switch语句:

#define TOPLEVEL_CASE(maR, maUnused, maType) \
  case maType: \
    switch (type2) { \
      BOOST_PP_SEQ_FOR_EACH_R(maR, NESTED_CASE, maType, TYPES_SUPPORTED) \
    } \
    break;

#define NESTED_CASE(maR, maToplevelType, maNestedType) \
  case maNestedType: /* do whatever you need, with maToplevelType and maNestedType */; break;

switch (type1) {
  BOOST_PP_SEQ_FOR_EACH(TOPLEVEL_CASE, %%, TYPES_SUPPORTED)
}

#undef TOPLEVEL_CASE
#undef NESTED_CASE

在上面的代码中,ma用作m acro a参数的前缀。此外,%%用于表示未使用该值。两者都只是我的个人约定。

ONE_CASETOPLEVEL_CASENESTED_CASE是“函数”BOOST_PP_SEQ_FOR_EACH的代码基本子例程。在其中,maType(或maToplevelTypemaNestedType)将引用当前“已选择”的枚举数的编译时间限定符。

答案 1 :(得分:1)

您可以让编译器在编译时生成一个查找表,该表存储用于在每个枚举组合之间进行转换的函数指针。

在运行时,将查询此查找表并执行返回的转换器函数。

下面的代码为您的示例实现了这个概念:

#include <iostream>
#include <utility>
#include <array>
#include <functional>
#include <stdexcept>

#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::make_scaled_unit;
using boost::units::scale;
using boost::units::static_rational;
namespace si = boost::units::si;


struct LengthUnit
{
    enum class Enum
    {
        MILLIMETRE,
        MICROMETRE,
        NANOMETRE
    };

    // this allows safe iteration over all possible enum values
    static constexpr std::array<Enum,3> all = {Enum::MILLIMETRE, Enum::MICROMETRE, Enum::NANOMETRE};
};


struct Quantity
{
  double value;
  LengthUnit::Enum unit;
};


template<LengthUnit::Enum>
struct UnitProperties;

template<>
struct UnitProperties<LengthUnit::Enum::MILLIMETRE>
{
  typedef make_scaled_unit<si::length,scale<10,static_rational< -3> > >::type unit_type;
};

template<>
struct UnitProperties<LengthUnit::Enum::MICROMETRE>
{
  typedef make_scaled_unit<si::length,scale<10,static_rational< -6> > >::type unit_type;
};

template<>
struct UnitProperties<LengthUnit::Enum::NANOMETRE>
{
  typedef make_scaled_unit<si::length,scale<10,static_rational< -9> > >::type unit_type;
};



template <typename EnumHolder>
struct Converter;

// specialization for LengthUnit 
template <>
struct Converter<LengthUnit>
{

using FunctionPtr = double(*)(double);

template <LengthUnit::Enum SrcUnit, LengthUnit::Enum DestUnit>
static double convert(double value)
{
  typedef typename UnitProperties<SrcUnit>::unit_type src_unit_type;
  typedef typename UnitProperties<DestUnit>::unit_type dest_unit_type;

  using boost::units::quantity;
  using boost::units::quantity_cast;

  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);
}
};

template <typename EnumHolder, typename FunctionPtr, std::size_t... Is>
constexpr auto make_lookup_table_impl(std::index_sequence<Is...>) -> std::array<FunctionPtr, sizeof...(Is)>
{
    constexpr std::size_t size = EnumHolder::all.size();
    return { Converter<EnumHolder>::template convert<EnumHolder::all[Is/size], EnumHolder::all[Is%size]>... };
}

template <typename EnumHolder>
constexpr auto make_lookup_table()
{
    constexpr std::size_t size = EnumHolder::all.size();
    using FunctionPtr = typename Converter<EnumHolder>::FunctionPtr;
    return make_lookup_table_impl<EnumHolder, FunctionPtr>(std::make_index_sequence<size*size>{});
}

template <typename EnumHolder, typename... Args>
auto convert(typename EnumHolder::Enum e1, typename EnumHolder::Enum e2, Args&... args)
{
    static constexpr auto table = make_lookup_table<EnumHolder>();
    static constexpr std::size_t size = EnumHolder::all.size();

    using utype = typename std::underlying_type<typename EnumHolder::Enum>::type;
    const std::size_t index = static_cast<utype>(e1)*size + static_cast<utype>(e2);

    if (index >= size*size)
    {
        throw std::invalid_argument("combination of enum values is not valid");
    }
    return table[index](std::forward<Args>(args)...);
}

int main()
{
  Quantity q { 34.5, LengthUnit::Enum::MICROMETRE };

  auto new_value = convert<LengthUnit>(q.unit, LengthUnit::Enum::NANOMETRE, q.value);

  Quantity r{new_value, LengthUnit::Enum::NANOMETRE};

  std::cout << q.value << " micrometres is " << r.value << " nanometres\n";
}

live example

如果在索引错误的情况下省略抛出异常,则clang和gcc都设法内联函数指针调用: see assembly on godbolt