替代在switch语句中扩展模板 [英] Alternative to expanding templates in a switch statement

查看:418
本文介绍了替代在switch语句中扩展模板的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

这是运行时而不是编译时内省。我的问题是,它需要每一个枚举在switch语句,这是一个痛苦的维护。我现在有一种情况,我需要处理所有的两组枚举的组合,这将需要嵌套的switch语句,如果我要处理它如上所述。组合复杂度显然不会缩放,但同时我看不到一个好的方法来驱动每个组合的模板扩展,而不是显式地。这是一个使用这种组合扩展进行运行时单元转换的例子:

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

在其他情况下,我使用boost :: variant及其static_visitor驱动所有组合的扩展。这很好,但它可能不工作在这里 - 不同traits的类型可能是相同的,但具有不同的行为。除非可以在变量类型中对枚举值进行编码。

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?

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

Or would the Boost Preprocessor macros or C++11 variadic templates be able to provide a better solution here? Or maybe boost::mpl::foreach?

感谢任何建议,
Roger

Thanks for any suggestions, Roger

推荐答案

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

You can let the compiler generate a lookup table at compile time which stores function pointers for conversion between each enum combination.

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

At runtime this lookup table is queried and the returned converter function is executed.

下面的代码为你的例子实现了这个概念:

The code below implements this concept for your example:

#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示例

如果省略引发异常错误,两个clang和gcc管理函数指针调用内联: 请参阅godbolt上的程序集

If you omit throwing an exception in case the index is wrong, both clang and gcc manage to inline the function pointer call: see assembly on godbolt

这篇关于替代在switch语句中扩展模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆