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

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

问题描述

我用这类型的模式在我的code来处理各种事物的特征。首先,我有一组特征模板;这些都是由一个枚举值专业:

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

然后我有code,它使用的特点。大多数情况下,它使用它们直接的,但在某些情况下,它需要在枚举值切换,例如:

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 ::变异及其static_visitor驱动所有组合的扩大。这种运作良好,但可能无法在这里工作 - 类型不同性状可能是相同的,但有不同的行为。除非是可能的EN code枚举值的变量类型?

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 preprocessor宏或C ++ 11可变参数模板可以在这里提供一个更好的解决方案?编辑:也许提高:: 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?

感谢您的任何建议,
罗杰

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.

下code实现了这个概念,你的例子:

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

<大骨节病> 活生生的例子

如果你省略抛出一个异常的情况下,该指数是错误的,既铛和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天全站免登陆