【问题标题】:Better pattern for partial specialization disambiguation precedence chain?部分专业化消歧优先链的更好模式?
【发布时间】:2016-08-07 22:32:46
【问题描述】:

考虑以下一系列偏特化:

template <typename T, typename Enable=void>
struct foo {
  void operator()() const { cout << "unspecialized" << endl; }
};

template <typename T>
struct foo<T, enable_if_t<
  is_integral<T>::value
>>{
  void operator()() const { cout << "is_integral" << endl; }
};

template <typename T>
struct foo<T, enable_if_t<
  sizeof(T) == 4
    and not is_integral<T>::value
>>{
  void operator()() const { cout << "size 4" << endl; }
};

template <typename T>
struct foo<T, enable_if_t<
  is_fundamental<T>::value
    and not (sizeof(T) == 4)
    and not is_integral<T>::value
>>{
  void operator()() const { cout << "fundamental" << endl; }
};

// etc...   

Live Demo

我经常看到这种事情(事实上,another StackOverflow answer elsewhere 为类似问题提供了相同的模式)。虽然这可行,但此代码存在一些严重的可维护性问题,并且还排除了,例如,如果上面的代码在库中,则具有更高优先级的用户级部分专业化。表达这个想法的更好模式是什么?我觉得必须有一些东西(可能涉及继承和可变参数模板参数?)可以更清晰和可维护地表达这个想法。 (假设每个特化都是一个完整的类而不是一个简单的函子,所以重载的函数不能以简单的方式工作)。

【问题讨论】:

    标签: c++ templates idioms partial-specialization


    【解决方案1】:

    条件计数的过度增长可以通过辅助结构来解决:

    #include <iostream>
    #include <type_traits>
    
    using namespace std;
    
    template <bool ThisCondition, class ParentCondition = void, class = void>
    struct condition_resolver {
       static constexpr bool is_condition_resolver = true;
       static constexpr bool parent_condition_v = !ThisCondition;
       static constexpr bool value = ThisCondition;
    };
    
    template <bool ThisCondition, class ParentCondition>
    struct condition_resolver<ThisCondition, ParentCondition, enable_if_t<ParentCondition::is_condition_resolver> > {
       static constexpr bool is_condition_resolver = true;
       static constexpr bool parent_condition_v = !ThisCondition && ParentCondition::parent_condition_v;
       static constexpr bool value = ThisCondition && ParentCondition::parent_condition_v;
    };
    
    template <typename T, typename Enable=void>
    struct foo {
      void operator()() const { cout << "unspecialized" << endl; }
    };
    
    template <typename T>
    struct is_integral_foo: condition_resolver<is_integral<T>::value> { };
    
    template <typename T>
    struct foo<T, enable_if_t<is_integral_foo<T>::value>>{
      void operator()() const { cout << "is_integral" << endl; }
    };
    
    template <typename T>
    struct has_size_four_foo: condition_resolver<sizeof(T) == 4, is_integral_foo<T>> { };
    
    template <typename T>
    struct foo<T, enable_if_t< has_size_four_foo<T>::value>>{
      void operator()() const { cout << "size 4" << endl; }
    };
    
    template <typename T>
    struct is_fundamental_foo: condition_resolver<is_fundamental<T>::value, has_size_four_foo<T>> { };
    
    template <typename T>
    struct foo<T, enable_if_t<is_fundamental_foo<T>::value>>{
      void operator()() const { cout << "fundamental" << endl; } 
    };
    
    typedef char four_sized[4];
    
    int main() {
       foo<int>()();
       foo<four_sized>()();
       foo<nullptr_t>()();
    }
    

    输出:

    is_integral
    size 4
    fundamental
    

    附言。 请记住,void 这也是基本的,会导致编译器产生一个警告 sizeof(void) 被认为...

    编辑:

    如果您确实需要使用专业化来解决条件过度增长问题,您可能会感兴趣:

    #include <iostream>
    #include <type_traits>
    
    using namespace std;
    
    template <class Tag, int Level, class... Args>
    struct concrete_condition_resolver;
    
    
    template <class Tag, int Level, class... Args>
    struct condition_resolver;
    
    template <class ConditionResolver>
    struct condition_resolver_parent {
       template<class CR = ConditionResolver>
       constexpr enable_if_t<CR::level != 0, bool> operator()(bool parent) {
          return (!parent && static_cast<const ConditionResolver*>(this)->condition && typename ConditionResolver::LevelUp()(true)) ||
                 (parent && !static_cast<const ConditionResolver*>(this)->condition && typename ConditionResolver::LevelUp()(true));
       }
    
       template<class CR = ConditionResolver>
       constexpr enable_if_t<CR::level == 0, bool> operator()(bool parent) {
          return (!parent && static_cast<const ConditionResolver*>(this)->condition) ||
                 (parent && !static_cast<const ConditionResolver*>(this)->condition);
       }
    };
    
    template <class Tag, int Level, class... Args>
    struct condition_resolver: concrete_condition_resolver<Tag, Level, Args...>, condition_resolver_parent<condition_resolver<Tag, Level, Args...>> {
       using LevelUp = condition_resolver<Tag, Level - 1, Args...>;
       using tag = Tag;
       static constexpr int level = Level;
       constexpr condition_resolver() {}
    };
    
    
    struct foo_tag { };
    
    template <class First, class... Args>
    struct concrete_condition_resolver<foo_tag, 0, First, Args...> {
       static constexpr bool condition = is_integral<First>::value;
    };
    
    template <class First, class... Args>
    struct concrete_condition_resolver<foo_tag, 1, First, Args...> {
       static constexpr bool condition = sizeof(First) == 4;
    };
    
    template <class First, class... Args>
    struct concrete_condition_resolver<foo_tag, 2, First, Args...> {
       static constexpr bool condition = is_fundamental<First>::value;
    };
    
    template <typename T, typename = void>
    struct foo;
    
    template <typename T>
    struct foo<T, enable_if_t<condition_resolver<foo_tag, 0, T>()(false)>>{
      void operator()() const { cout << "is_integral" << endl; }
    };
    
    template <typename T>
    struct foo<T, enable_if_t<condition_resolver<foo_tag, 1, T>()(false)>>{
      void operator()() const { cout << "size 4" << endl; }
    };
    
    template <typename T>
    struct foo<T, enable_if_t<condition_resolver<foo_tag, 2, T>()(false)>>{
      void operator()() const { cout << "is_fundamental" << endl; }
    };
    
    
    typedef char four_sized[4];
    
    int main() {
       foo<int>()();
       foo<four_sized>()();
       foo<nullptr_t>()();
    }
    

    这种方法甚至适用于使用 enable_if 的重载函数,而部分特化只处理结构......

    【讨论】:

    • 看起来是一种非常简洁的管理条件的方法。我打算提出类似的建议,但我的会比你的笨拙一些。 +1
    • 这比template &lt;typename T&gt; using condition1_t = std::integral_constant&lt;bool, sizeof(T)==4 &amp;&amp; condition0_t&lt;T&gt;::value&gt;; 之类更简单的东西有什么好处(然后类似地定义condition_2,但引用condition_1 而不是condition_0
    • @DavidHollman 实际上你不能只使用一个 using 来做到这一点,因为它包含最后一个条件的未否定部分值......例如condition1_t 包含sizeof(T)==4,如果应该适合在condition2_t 中使用,它应该是sizeof(T)!=4...
    • @DavidHollman 当然你可以把它分成两个用途,但它真的比那种方法更简单吗?
    • @WojciechFrohmberg 当然。我的布尔逻辑思维放屁
    【解决方案2】:

    我为什么要回答自己的问题

    所以自从问了这个问题后,我就一直被这个问题困扰着,而且我从来没有对最初的答案完全满意。经过多次摆弄和试验/错误,我想出了一个我更满意的模式,它使用标签调度。它是否实际上比之前的答案更好、更易读、更易于维护由你来判断,但我更喜欢它。随意把它拆开,批评它,打破它。 :-)

    基础版

    事不宜迟,这是解决问题最简单版本的代码

    template <typename> struct always_true : true_type { };
    template <typename> struct always_false : false_type { };
    
    template <typename T, template <class...> class condition=always_false,
      typename flag=integral_constant<bool, condition<T>::value>
    >
    struct foo;
    
    ////////////////////////////////////////
    // "unspecialized" version
    
    // put always_true and false_type together here so that no one gets here accidentally
    template <typename T, typename true_or_false_type>
    struct foo<T, always_true, true_or_false_type> {
      void operator()() const { cout << "unspecialized" << endl; }
    };
    
    ////////////////////////////////////////
    // is_fundamental
    
    template <typename T>
    struct foo<T, is_fundamental, true_type> {
      void operator()() const { cout << "is_fundamental" << endl; }
    };
    template <typename T> struct foo<T, is_fundamental, false_type> : foo<T, always_true> { };
    
    ////////////////////////////////////////
    // is_integral
    
    template <typename T>
    struct foo<T, is_integral, true_type> {
      void operator()() const { cout << "is_integral" << endl; }
    };
    template <typename T>
    struct foo<T, is_integral, false_type> : foo<T, is_fundamental> { };
    
    ////////////////////////////////////////
    // sizeof(T) == 4
    
    template <typename T>
    using size_is_4 = integral_constant<bool, sizeof(T) == 4>;
    
    template <typename T>
    struct foo<T, size_is_4, true_type> {
      void operator()() const { cout << "size_is_4" << endl; }
    };
    template <typename T>
    struct foo<T, size_is_4, false_type> : foo<T, is_integral> { };
    
    ////////////////////////////////////////
    // Now put the most specialized condition in the base of this template
    
    template <typename T, typename true_or_false_type>
    struct foo<T, always_false, true_or_false_type> : foo<T, size_is_4> { };
    

    在上一个答案中的辅助结构中保存的优先级链是在继承中编码的。

    更多花里胡哨

    添加启用用户部分特化的能力比库的优先级更高,但原理是一样的。完整版在此demo

    【讨论】:

    • 您的解决方案很有趣,您可能还想看看我编辑的方法。它使用专业化和标签调度类型......
    • @WojciechFrohmberg 谢谢。我认为无论哪种方法“更好”(对于更好的一些定义),在这里都有两种方法是有用的。感谢您为回答这个问题付出的时间和精力
    猜你喜欢
    • 2017-04-14
    • 1970-01-01
    • 2020-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    相关资源
    最近更新 更多