【问题标题】:Partial function/method template specialization workarounds部分函数/方法模板专业化解决方法
【发布时间】:2015-02-18 23:49:45
【问题描述】:

我知道函数和类方法不支持部分模板特化,所以我的问题是:解决这个问题的常见解决方案或模式是什么?下面的Derived 派生自Base,并且这两个类都有虚拟方法greet()speak()Foo 持有 std::array<unique_ptr<T>, N> 并在 do_something() 中使用。 Foo 有两个模板参数:T(类类型)和Nstd::array 的元素数) 如果N = 2,则存在do_something() 的高度优化版本。现在假设FooT 参数并不总是基类Base。理想情况下,我想编写以下代码,但它是非法的:

//ILLEGAL
template<typename T>
void Foo<T,2>::do_something()
{
  arr_[0]->greet();
}

以下是完整代码和我当前(丑陋的)解决方案。我必须对do_something() 进行两次专业化,一次用于Base,一次用于Derived。如果存在像do_something() 这样可以在特殊的N=2 情况下优化的多种方法,并且如果存在Base 的许多子类,这将变得很难看。

#include <iostream>
#include <memory>

class Base
{
public:
  virtual void speak()
  {
    std::cout << "base is speaking" << std::endl;  
  }
  virtual void greet()
  {
    std::cout << "base is greeting" << std::endl;  
  }
};

class Derived : public Base
{
public:
  void speak()
  {
    std::cout << "derived is speaking" << std::endl;  
  }
  void greet()
  {
    std::cout << "derived is greeting" << std::endl;  
  }
};

template<typename T, int N>
class Foo
{
public:
  Foo(std::array<std::unique_ptr<T>, N>&& arr) :
    arr_(std::move(arr))
  {
  }

  void do_something();

  std::array<std::unique_ptr<T>, N> arr_;
};

template<typename T, int N>
void Foo<T,N>::do_something()
{
  arr_[0]->speak();
}

//Want to avoid "copy-and_paste" of do_something() below
template<>
void Foo<Base,2>::do_something()
{
  arr_[0]->greet();
}

template<>
void Foo<Derived,2>::do_something()
{
  arr_[0]->greet();
}

int main()
{
  constexpr int N = 2;
  std::array<std::unique_ptr<Derived>, N> arr = 
    {
      std::unique_ptr<Derived>(new Derived),
      std::unique_ptr<Derived>(new Derived)
    };
  Foo<Derived, N> foo(std::move(arr));
  foo.do_something();
  return 0;
}

【问题讨论】:

    标签: c++ templates inheritance template-specialization partial-specialization


    【解决方案1】:

    有不同的替代方案,具体取决于问题中的其他约束条件,其中一个可能比另一个更合适。

    第一个是将请求转发到模板类中的静态函数,它允许部分特化:

    template <int N>
    struct Helper {
       template <typename T>
       static void talk(T& t) {  // Should be T const &, but that requires const members
           t.speak();
       }
    };
    template <>
    struct Helper<2> {
       template <typename T>
       static void talk(T& t) {
           t.greet();
       }
    }
    

    ;

    那么do_something 的实现将是:

    template <typename T, int N>
    void Foo<T,N>::do_something() {
       Helper<N>::talk(*arr_[0]);
    }
    

    或者,您可以使用标签调度来选择多个重载之一:

    template <int N> struct tag {};
    
    template <typename T, int N>
    template <int M>
    void Foo<T,N>::do_something_impl(tag<M>) {
        arr_[0]->speak();
    }
    
    template <typename T, int N>
    void Foo<T,N>::do_something_impl(tag<2>) {
        arr_[0]->greet();
    }
    
    template <typename T, int N>
    void Foo<T,N>::do_something() {
        do_something_impl(tag<N>());
    }
    

    我创建了一个标签类型,可以专门用于任何可能的N。您还可以使用 C++11 中的现有工具。

    最后,如果您需要为不同的功能做类似的事情,您可以使用继承,并将一些功能推送到解决差异的基础。这可以通过将通用代码推送到基础,将差异推送到中间级别并使用仅从其余部分继承的较低级别的 front 类型来完成(基础包含通用代码,派生类型专门化)。或者使用 CRTP(基础包含差异、派生类型通用代码并从基础中提取特定实现。

    【讨论】:

    • 我有兴趣了解更多。您能否将我与“使用 C++ 中的现有工具”联系起来。谢谢。
    • @anon: std::integral_constant,例如,可用于标签调度,适合这个特殊问题的需要。
    【解决方案2】:

    诀窍是将实现转发到帮助模板类,并部分专门化该类和/或使用标签调度:

    namespace {
        template<typename T, int N, bool isBase = std::is_base_of<Base, T>::value>
        struct helper {
            // general case: 
            void operator () (std::array<std::unique_ptr<T>, N>& arr_) const
            {
                arr_[0]->speak();
            }
        };
    
        template<typename T>
        struct helper<T, 2, true>
        {
            void operator () (std::array<std::unique_ptr<T>, 2>& arr_) const
            {
              arr_[0]->greet();
            }
        };
    
        // You may add other specialization if required.
    
    }
    
    template<typename T, int N>
    void Foo<T,N>::do_something()
    {
        helper<T, N>()(arr_);
    }
    

    【讨论】:

    • 你会如何使用标签调度?一个大纲就足够了。
    • @kec:使用std::is_base_of&lt;Base, T&gt;::type作为额外参数(因此std::true_typestd::false_type重载)并删除模板参数isBase
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多