c++类模板专门化,无需重新实现所有内容

c++ class template specialization, without having to reimplement everything

本文关键字:实现 新实现 专门化 c++      更新时间:2023-10-16

我有一个模板化的类,像这样:

template<typename T>
class A
{
    protected:
    std::vector<T> myVector;
    public:
    /*
    constructors + a bunch of member functions here
    */
}

我想添加一个成员函数,只适用于1种给定类型的T.是否有可能做到这一点,而不必专门化类并重新实现所有其他已经存在的方法?

谢谢

最简单、最干净的解决方案是在方法体中使用static_assert(),拒绝除所选类型之外的其他类型(在下面的示例中只接受整数):

#include <type_traits>  
#include <vector>
template <typename T>
class A
{
public:
    void onlyForInts(T t)
    {
        static_assert(std::is_same<T, int>::value, "Works only with ints!");
    }
protected:
    std::vector<T> myVector;
};
int main()
{
    A<int> i;
    i.onlyForInts(1); // works !
    A<float> f;
    //f.onlyForInts(3.14f); // does not compile !
}

OK CASE DEMONOK CASE DEMO

这利用了这样一个事实,即编译器只在实际使用时才实例化类模板的成员函数(而不是在类模板本身实例化时)。使用上述解决方案,当编译器尝试这样做时,由于执行static_assert而失败。

c++标准参考:

§14.7.1隐式实例化[temp.inst]

  • 除非函数模板专门化已被显式实例化或显式特化,否则函数模板专门化在需要存在函数定义的上下文中被引用时将被隐式实例化。除非调用函数模板显式特化或显式特化类模板的成员函数,否则在需要默认实参值的上下文中调用函数模板或类模板的成员函数时,将隐式实例化函数模板的默认实参或成员函数。

  • (例子:

    template<class T> struct Z {
      void f();
      void g();
    };
    void h() {
      Z<int> a;     // instantiation of class Z<int> required
      Z<char>* p;   // instantiation of class Z<char> not required
      Z<double>* q; // instantiation of class Z<double> not required
      a.f();        // instantiation of Z<int>::f() required
      p->g();       // instantiation of class Z<char> required, and
                    // instantiation of Z<char>::g() required
    }
    

    本例中不要求隐式地设置class Z<double>Z<int>::g()Z<char>::f()实例化。- 结束示例]

  • 是的,在c++ 03中使用CRTP(奇怪的循环模板模式)是可能的:

    #include <numeric>
    #include <vector>
    template<typename Derived, typename T>
    struct Base
    {
    };
    template<typename Derived>
    struct Base<Derived, int>
    {
        int Sum() const
        {
            return std::accumulate(static_cast<Derived const*>(this)->myVector.begin(), static_cast<Derived const*>(this)->myVector.end(), int());
        }
    };
    template<typename T>
    class A : public Base<A<T>, T>
    {
        friend class Base<A<T>, T>;
    protected:
        std::vector<T> myVector;
    public:
        /*
        constructors + a bunch of member functions here
        */
    };
    int main()
    {
        A<int> Foo;
        Foo.Sum();
    }
    

    作为另一种解决方案,它也适用于普通c++ 03(与static_assertenable_if解决方案相反),您可以添加额外的默认模板参数,从而使您同时拥有这两种解决方案类的特化和非特化版本。然后,您可以从非专门化版本继承专门化版本。

    下面是一个示例代码片段:
    #include <vector>
    template<typename T, bool unspecialized = false>
    class A
    {
      protected:
        std::vector<T> myVector;
      public:
        void setVec(const std::vector<T>& vec) { myVector = vec; }
        /*
        constructors + a bunch of member functions here
        */
    };
    template<>
    class A<int, false> : public A<int, true>
    {
      public: 
       int onlyForInt() {
          return 25;
       }
    };
    int main() {
      // your code goes here
      std::vector<int> vec;
      A<int> a;
      a.setVec(vec);
      a.onlyForInt();
      return 0;
    }
    

    这个解决方案的缺点是需要添加构造函数转发器,if类具有非平凡构造函数。

    @PiotrS的static_assert技术。很好地工作。但是知道您可以在不重复代码的情况下专门化单个成员函数也很好。只要给通用的onlyForInts()一个空的无操作实现,并对int进行类外专门化

    #include <vector>
    template <typename T>
    class A
    {
    public:
        void onlyForInts(T t)
        {
            // no-op
        }
    protected:
        std::vector<T> myVector;
    };
    template<>
    void A<int>::onlyForInts(int t)
    {
        // works  
    }
    int main()
    {
        A<int> i;
        i.onlyForInts(1); // works !
        A<float> f;
        f.onlyForInts(3.14f); // compiles, but does nothing !
    }
    
    <<p> 生活例子/strong>。

    如果您希望具有int特定的行为而不完全禁用通用行为,则此技术会派上用场。

    答案中尚未给出的一种方法是使用标准库std::enable_if在继承到定义适当成员函数的主类的基类上执行SFINAE。

    示例代码:

    template<typename T, class Enable = void>
    class A_base;
    template<typename T>
    class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>{
        public:
            void only_for_ints(){/* integer-based function */}
    };
    template<typename T>
    class A_base<T, typename std::enable_if<!std::is_integral<T>::value>::type>{
        public:
            // maybe specialize for non-int
    };
    template<typename T>
    class A: public A_base<T>{
        protected:
            std::vector<T> my_vector;
    };
    

    这种方法将比一个空函数更好,因为你对你的API更严格,比static_cast更好,因为它根本不会使它进入函数的内部(它将不存在),并且会在编译时给你一个漂亮的错误消息(GCC显示"没有名为' only_for_int '的成员"在我的机器上)。

    这种方法的缺点是编译时间和代码膨胀,但我不认为它太沉重。

    (你敢说c++ 11的要求是一个缺点,我们在2014年,该死的,下一个标准甚至已经完成了!)

    另外,我注意到,您可能必须在基类而不是final类中定义my_vector,因为您可能希望在成员函数中处理该数据。

    一个不需要重复一堆代码的好方法是创建一个基类,并在基类中继承这个类。

    的例子:

    template<typename T>
    class base_data{
        protected:
            std::vector<T> my_vector;
    };
    template<typename T>
    class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>: public base_bata<T>{
        public:
            void only_for_ints(){/* phew, finally. fiddle around with my_vector! */}
    };
    // non-integer A-base
    template<typename T>
    class A: public A_base<T>{
        protected:
            // helper functions not available in base
    };
    

    这确实留下了一个看起来很可怕的多重继承方案,但它非常可行,并且使基于模板参数定义成员变得容易(为了将来的验证)。

    人们通常不喜欢多重继承,也不喜欢SFINAE看起来多么复杂/混乱,但现在我知道了它,没有它我就活不下去了:静态代码的速度与动态代码的多态性!

    不确定我在哪里找到这个,但是您可以使用= delete;作为类内的函数定义,从而删除一般情况下的函数,然后在类外显式地专门化:

    template <typename T>
    struct A
    {
      auto int_only(T) -> void = delete;
    };
    template <> auto A<int>::int_only(int) -> void {}
    int main()
    {
      auto a_int = A<int>{};
      auto a_dbl = A<double>{};
      a_int.int_only(0);
      // a_dbl.int_only(3.14);  error: call to deleted member function
    }
    

    https://en.cppreference.com/w/cpp/language/function Deleted_functions