C++类模板特化,无需重新实现全部内容

28

我有一个类似于模板的类:

template<typename T>
class A
{
    protected:
    std::vector<T> myVector;

    public:
    /*
    constructors + a bunch of member functions here
    */
}

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

为什么要使用protected而不是private数据? - TemplateRex
2
如果@TemplateRex想要继承这个类并对my_vector进行有意义的操作,可以使用此模板。 - RamblingMad
6个回答

21
最简单、最清晰的解决方案是在方法体中使用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 DEMO NOK CASE DEMO

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

C++标准参考:

§ 14.7.1 Implicit instantiation [temp.inst]

  1. Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist. Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.

  2. [ Example:

    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
    }
    

    Nothing in this example requires class Z<double>, Z<int>::g(), or Z<char>::f() to be implicitly instantiated. — end example ]


这个解决方案到底有多干净还有待商榷,因为它允许获取函数地址,即使不允许调用该函数。 - Fytch
1
@Asfdlol: 不是这样的。这需要函数被实例化,而编译器无法做到。 - Cornstalks
@Asfdlol:如果您想允许两种、三种或N种类型,该怎么办?比较我的解决方案(在一个可读条件中添加替代方案)和您的解决方案(完全不可能?)。 - Piotr Skotnicki
谢谢 Piotr。这看起来确实是一个非常简单和可扩展的解决方案,可以解决我的问题。 - bob kaggle
1
static_assert 提供了一个很好的错误信息,但是也很好知道你可以专门为单个成员函数进行特化而不会出现代码重复。只需给通用的 onlyForInts() 函数提供一个空的 no-op 实现,并在类外部为 int 进行特化:http://coliru.stacked-crooked.com/a/2f790496fb1b3cf3 - TemplateRex

13

使用C++03和CRTP(奇异递归模板模式)是可能的:Curiously recurring template pattern

#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();
}

17
如果有人能解释为什么他们要给我负评,这样我就可以从我的错误中学习,我会非常感激。 - Fytch
我没有给这个点踩,但如果我猜的话,我会说可能是因为它太复杂了。 - Ryan Reich
2
非常感谢,这太棒了,我之前不知道! - ToxiCore

5
作为一种替代方案,这个解决方法同样适用于普通的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;
}

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

2
在C++03中,您需要实现所有构造函数两次(因为无法继承构造函数),并且由于辅助模板参数,您还需要编写大量操作符的样板代码。 - Fytch
@Asfdlol:是的,没错。这是这种方法的一个不利因素。 - Krizz
@pqnet:我并没有说过它不会。 - Krizz

4
< p > @PiotrS. 的 static_assert 技术非常好用。但是,如果你能够在不重复代码的情况下专门为单个成员函数进行特化,那也是很好的。只需给通用的 onlyForInts() 函数提供一个空的 no-op 实现,并在类外专门为 int 进行特化即可。 < /p >
#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 !
}

实时示例

如果您想在不完全禁用通用行为的情况下具有 int 特定行为,此技术非常有用。


+1 我喜欢这种方法,但它让程序员做一些非常反直觉的事情,可能应该使用某种形式的SFINAE来处理,而不是让他们自己去做。 - RamblingMad
我没有看到PiotrS发布的任何答案。在你的回答中,使用Ctrl+F搜索PiotrS只有一个结果。 - undefined
@KulaGGin 看看上面被接受的答案,用户名已更改为Piotr Skotnicki。 - undefined

4

还没有在答案中提到的一种方法是使用标准库 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在我的机器上显示“has no member named ‘only_for_ints’”)。
这种方法的缺点是编译时间和代码膨胀,但我认为它不太重。
(不要说C++11要求是一个缺点,我们已经到2014年了,下一个标准甚至已经被确定了!)
另外,我注意到,你可能需要在基类中定义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看起来复杂/混乱,但自从我了解了它后,我就再也无法离开它了:具有静态代码速度的动态代码的多态性!


2

我不确定我在哪里找到的,但是你可以在类内使用= 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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接