Alternative au développement de modèles dans une instruction switch

J’utilise ce type de modèle dans mon code pour gérer les traits pour diverses choses. Tout d’abord, j’ai un ensemble de modèles de traits; ceux-ci sont spécialisés par une valeur enum:

template struct PixelProperties; /// Properties of UINT8 pixels. template struct PixelProperties : public PixelPropertiesBase<PixelProperties > { /// 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; }; 

J’ai ensuite un code qui utilise les traits. La plupart du temps, il les utilise directement, mais dans certains cas, il doit activer la valeur enum, par exemple:

 bool isComplex(::ome::xml::model::enums::PixelType pixeltype) { bool is_complex = false; switch(pixeltype) { case ::ome::xml::model::enums::PixelType::INT8: is_complex = PixelProperties::is_complex; break; case ::ome::xml::model::enums::PixelType::INT16: is_complex = PixelProperties::is_complex; break; [...] } return is_complex; } 

C’est pour l’exécution au lieu de l’introspection au moment de la compilation. Mon problème est que cela nécessite que chaque énum soit pris en compte dans l’instruction switch, ce qui est difficile à maintenir. J’ai maintenant une situation où je dois gérer toutes les combinaisons de deux ensembles d’énums, et cela nécessiterait des instructions de commutateur nestedes si je devais le gérer comme ci-dessus. La complexité combinatoire ne va évidemment pas s’amplifier, mais en même temps, je ne vois pas comment diriger le développement des modèles pour chaque combinaison autrement qu’explicitement. Ceci est un exemple artificiel d’utilisation d’un tel développement combinatoire pour la conversion d’unités d’exécution:

 #include  #include  #include  #include  #include  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 struct UnitProperties; template struct UnitProperties { typedef make_scaled_unit<si::length,scale<10,static_rational > >::type unit_type; }; template struct UnitProperties { typedef make_scaled_unit<si::length,scale<10,static_rational > >::type unit_type; }; template struct UnitProperties { typedef make_scaled_unit<si::length,scale<10,static_rational > >::type unit_type; }; struct Quantity { double value; LengthUnit unit; }; template double convert(double value) { typedef typename UnitProperties::unit_type src_unit_type; typedef typename UnitProperties::unit_type dest_unit_type; quantity src(quantity::from_value(value)); quantity dest(src); return quantity_cast(dest); } Quantity convert(Quantity q, LengthUnit newunit) { switch(q.unit) { case MILLIMETRE: switch(newunit) { case MILLIMETRE: return Quantity({convert(q.value), MILLIMETRE}); break; case MICROMETRE: return Quantity({convert(q.value), MICROMETRE}); break; case NANOMETRE: return Quantity({convert(q.value), NANOMETRE}); break; } break; case MICROMETRE: switch(newunit) { case MILLIMETRE: return Quantity({convert(q.value), MILLIMETRE}); break; case MICROMETRE: return Quantity({convert(q.value), MICROMETRE}); break; case NANOMETRE: return Quantity({convert(q.value), NANOMETRE}); break; } break; case NANOMETRE: switch(newunit) { case MILLIMETRE: return Quantity({convert(q.value), MILLIMETRE}); break; case MICROMETRE: return Quantity({convert(q.value), MICROMETRE}); break; case NANOMETRE: return Quantity({convert(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"; } 

Dans d’autres situations, j’utilise boost :: variant et son static_visitor pour piloter le développement de toutes les combinaisons. Cela fonctionne bien, mais cela pourrait ne pas fonctionner ici – les types de traits différents peuvent être les mêmes mais avoir un comportement différent. Sauf s’il est possible de coder les valeurs enum dans le type variant?

Ou les macros du préprocesseur Boost ou les modèles variadiques C ++ 11 pourraient-ils fournir une meilleure solution ici? Edit: Ou peut-être boost :: mpl :: foreach?

Merci pour toutes suggestions, Roger

Boost.Preprocessor vous permettrait de masquer la complexité combinatoire.

Pour l’utiliser, vous devez déplacer la définition “principale” de la liste de types dans un type de données Boost.Preprocessor. J’aime les séquences, je vais donc en utiliser une:

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

Ceci serait alors utilisé pour générer l’énumération:

 enum class PixelType { BOOST_PP_SEQ_ENUM(TYPES_SUPPORTED) }; 

Et pour envelopper une instruction 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 

Ou enroulez une déclaration de switch “quadratique”:

 #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 

Dans le code ci-dessus, ma est utilisé comme préfixe pour les arguments multiples. De plus, %% est utilisé pour indiquer que la valeur n’est pas utilisée. Les deux ne sont que ma convention personnelle.

Les macros ONE_CASE , TOPLEVEL_CASE et NESTED_CASE sont votre code. Il s’agit en réalité de sous-routines de la “fonction” BOOST_PP_SEQ_FOR_EACH . À l’intérieur, maType (ou maToplevelType et maNestedType ) fera référence aux indétificateurs de compilation des énumérateurs actuellement “choisis”.

Vous pouvez laisser le compilateur générer une table de recherche au moment de la compilation, qui stocke les pointeurs de fonction pour la conversion entre chaque combinaison enum.

Au moment de l’exécution, cette table de recherche est interrogée et la fonction de conversion renvoyée est exécutée.

Le code ci-dessous implémente ce concept pour votre exemple:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  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 all = {Enum::MILLIMETRE, Enum::MICROMETRE, Enum::NANOMETRE}; }; struct Quantity { double value; LengthUnit::Enum unit; }; template struct UnitProperties; template<> struct UnitProperties { typedef make_scaled_unit > >::type unit_type; }; template<> struct UnitProperties { typedef make_scaled_unit > >::type unit_type; }; template<> struct UnitProperties { typedef make_scaled_unit > >::type unit_type; }; template  struct Converter; // specialization for LengthUnit template <> struct Converter { using FunctionPtr = double(*)(double); template  static double convert(double value) { typedef typename UnitProperties::unit_type src_unit_type; typedef typename UnitProperties::unit_type dest_unit_type; using boost::units::quantity; using boost::units::quantity_cast; quantity src(quantity::from_value(value)); quantity dest(src); return quantity_cast(dest); } }; template  constexpr auto make_lookup_table_impl(std::index_sequence) -> std::array { constexpr std::size_t size = EnumHolder::all.size(); return { Converter::template convert... }; } template  constexpr auto make_lookup_table() { constexpr std::size_t size = EnumHolder::all.size(); using FunctionPtr = typename Converter::FunctionPtr; return make_lookup_table_impl(std::make_index_sequence{}); } template  auto convert(typename EnumHolder::Enum e1, typename EnumHolder::Enum e2, Args&... args) { static constexpr auto table = make_lookup_table(); static constexpr std::size_t size = EnumHolder::all.size(); using utype = typename std::underlying_type::type; const std::size_t index = static_cast(e1)*size + static_cast(e2); if (index >= size*size) { throw std::invalid_argument("combination of enum values is not valid"); } return table[index](std::forward(args)...); } int main() { Quantity q { 34.5, LengthUnit::Enum::MICROMETRE }; auto new_value = convert(q.unit, LengthUnit::Enum::NANOMETRE, q.value); Quantity r{new_value, LengthUnit::Enum::NANOMETRE}; std::cout << q.value << " micrometres is " << r.value << " nanometres\n"; } 

exemple en direct

Si vous omettez de lancer une exception au cas où l'index est incorrect, clang et gcc parviennent à insérer en ligne l'appel du pointeur de la fonction: voir assembly on godbolt