【问题标题】:Combine multiple class template specializations结合多个类模板特化
【发布时间】:2018-07-01 04:30:37
【问题描述】:

鉴于下面的示例,压缩模板特化以使一两条指令足以定义所有特殊值的最佳方法是什么?也许是可变参数?

enum class PositiveDigit // Not necessarily sequential
{ One=1, Two, Three, Four, Five, Six, Seven, Eight, Nine };

// Base class template
template <std::underlying_type_t<PositiveDigit>>
struct IsNum
{ static constexpr bool aPrimeDigit = false; };

// Specialized class templates to define which positive digits are prime 
// HOW TO BEST COMBINE/CONDENSE THESE? VARIADIC?? Ideally, something like:
//    template <PositiveDigit::Two, PositiveDigit::Three, ……> ……   OR,
//    template <> …… (PositiveDigit::Two, PositiveDigit::Three, ……) ……   OR??
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Two)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Three)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Five)>
{ static constexpr bool aPrimeDigit = true; };
template <>
struct IsNum<static_cast<std::underlying_type_t<PositiveDigit>>(PositiveDigit::Seven)>
{ static constexpr bool aPrimeDigit = true; };

int main() {
    // Note: It's perfectly okay to pass integers beyond the range of the
    //  enum class, they'll simply provide a false result
    IsNum<-5>::aPrimeDigit; // false
    IsNum<13>::aPrimeDigit; // false
    IsNum< 7>::aPrimeDigit; // true!
}

请假设enum 必须保持强类型。现实世界的问题有很大的enum class,许多潜在的专业化,与数字或素数无关;这只是一个简单的例子。

这些类似的问题似乎无法解决手头的问题(除非我遗漏了什么):

【问题讨论】:

  • 有趣的是零是正数,一是质数。
  • 看看如何测试一个值是否在integer_sequence中,然后写一个模板取integer_sequence的素数?哦,还有一个不是素数。
  • 好的,已修复以安抚迂腐。还要提到枚举不一定是顺序的

标签: c++ c++11 templates c++14 variadic-templates


【解决方案1】:

为了取笑可变参数模板,我提出了几个解决方案。

它们都基于 constexpr 函数,该函数表示一个值是否在模板可变参数列表中(如 aschepler 的答案中的 value_in_list,但这也适用于 C++14)

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t0 == ts... };

   return ret;
 }

第一个与 aschepler 的解决方案 (+1) 非常相似,并且不使用模板特化。

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{ 
   static constexpr bool aPrimeDigit
      = isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
                 PositiveDigit::Five, PositiveDigit::Seven>
                    (static_cast<PositiveDigit>(I));
};

在我看来更简单,但如果你真的(真的!)想要通过模板专业化,你可以写如下内容

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
 { static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
   PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
   PositiveDigit::Five, PositiveDigit::Seven>
      (static_cast<PositiveDigit>(I))>>
 { static constexpr bool aPrimeDigit = true; };

以下是完整的编译示例

#include <type_traits>

enum class PositiveDigit
{ Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine };

template <typename T, T ... ts>
constexpr bool isInList (T const & t0)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t0 == ts... };

   return ret;
 }

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum1
{ 
   static constexpr bool aPrimeDigit
      = isInList<PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
                 PositiveDigit::Five, PositiveDigit::Seven>
                    (static_cast<PositiveDigit>(I));
};

template <std::underlying_type_t<PositiveDigit>, typename = std::true_type>
struct IsNum2
 { static constexpr bool aPrimeDigit = false; };

template <std::underlying_type_t<PositiveDigit> I>
struct IsNum2<I, std::integral_constant<bool, isInList<
   PositiveDigit, PositiveDigit::Two, PositiveDigit::Three,
   PositiveDigit::Five, PositiveDigit::Seven>
      (static_cast<PositiveDigit>(I))>>
 { static constexpr bool aPrimeDigit = true; };

int main ()
 {
   static_assert( false == IsNum1<-5>::aPrimeDigit, "!" );
   static_assert( false == IsNum1<13>::aPrimeDigit, "!" );
   static_assert(  true == IsNum1< 7>::aPrimeDigit, "!" );

   static_assert( false == IsNum2<-5>::aPrimeDigit, "!" );
   static_assert( false == IsNum2<13>::aPrimeDigit, "!" );
   static_assert(  true == IsNum2< 7>::aPrimeDigit, "!" );
 }

【讨论】:

  • 不,不需要专业化,任何简洁的解决方案都可以。你的第一个建议肯定是有效的(而且简洁)。不过,我很困惑参数包如何进入isInList(),而函数参数中不存在ts。你能解释一下那个成语或给我指出一个好的资源吗?另外,知道有什么巧妙的方法可以避免使用未使用的数组吗?我的强迫症坚持要我问。
  • @quasinormalized - ts... 作为模板参数存在;您也可以在 C++11 和 C++14 中解压缩数组初始化中的可变参数列表。所以——举个例子——给定一个等于2, 3, 5, 7ts...,初始化变成(void)unused { false, ret |= t0 == 2, ret |= t0 == 3, ret |= t0 == 5, ret |= t0 == 7 };。观察第一个false:如果ts... 是一个空的可变参数列表,这是必要的。不幸的是,带有变量的函数在 C++11 中不能是 constexpr,所以这个解决方案只能从 C++14 开始工作。 (继续)。
  • @quasinormalized - 是的:我知道一种避免unused 数组的方法。您可以使用递归(也在 C++11 中)或简单地编写(注意:代码未测试)template &lt;typename T, T ... ts&gt; constexpr bool isInList (T const &amp; t0) { return ( (t0 == ts) || ... ); }。请注意,基本上,这是第一个阿舍普勒的解决方案(请参阅value_in_list)。这叫做“折叠”。它真的很优雅和强大。不幸的是,仅从 C++17 开始可用。
【解决方案2】:

你在寻找这样的东西吗?

C++17:

#include <type_traits>

template <auto Value, decltype(Value)... List>
struct value_in_list
    : public std::disjunction<std::bool_constant<Value==List>...> {};

template <std::underlying_type_t<PositiveDigit> N>
struct IsNum
{
    static constexpr bool aPrimeDigit =
        value_in_list<static_cast<PositiveDigit>(N),
            PositiveDigit::Two, PositiveDigit::Three,
            PositiveDigit::Five, PositiveDigit::Seven
        >::value;
};

或 C++14:

#include <type_traits>

template <typename T, T Value, T... List>
struct value_in_list;

// Base case 1: Value is not in an empty list.
template <typename T, T Value>
struct value_in_list<T, Value> : public std::false_type {};

// Base case 2: Value is in a list that starts with Value.
template <typename T, T Value, T... Rest>
struct value_in_list<T, Value, Value, Rest...>
    : public std::true_type {};

// Recursion case: If a non-empty list does not start with Value,
// Value is in the list if and only if it's in the list with the
// first element removed.
template <typename T, T Value, T First, T... Rest>
struct value_in_list<T, Value, First, Rest...>
    : public value_in_list<T, Value, Rest...> {};

template <std::underlying_type_t<PositiveDigit> N>
struct IsNum
{
    static constexpr bool aPrimeDigit =
        value_in_list<PositiveDigit, static_cast<PositiveDigit>(N),
            PositiveDigit::Two, PositiveDigit::Three,
            PositiveDigit::Five, PositiveDigit::Seven
        >::value;
};

【讨论】:

  • 我的编译器(VS2017,仅部分支持 C++17)不喜欢 auto,而且我对模板还不够熟练,无法找到解决方法。有什么想法吗?
  • 添加了 C++14 解决方案。如果没有 C++17 的 auto 模板参数,您通常必须将类型和值分成两个单独的模板参数。
  • 这是一个非常好的和有启发性的答案。我接受 max66 的 C++14 兼容答案只是为了简洁。这两个答案都加深了我对模板的理解。干杯!
猜你喜欢
  • 2021-09-11
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 2019-08-20
  • 1970-01-01
  • 1970-01-01
  • 2013-07-11
相关资源
最近更新 更多