将C++模板参数限制为子类

pha*_*t0m 72 c++ templates

如何强制模板参数T成为特定类的子类Baseclass?像这样的东西:

template <class T : Baseclass> void function(){
    T *object = new T();

}
Run Code Online (Sandbox Code Playgroud)

小智 80

使用符合C++ 11的编译器,您可以执行以下操作:

template<class Derived> class MyClass {

    MyClass() {
        // Compile-time sanity check
        static_assert(std::is_base_of<BaseClass, Derived>::value, "Derived not derived from BaseClass");

        // Do other construction related stuff...
        ...
   }
}
Run Code Online (Sandbox Code Playgroud)

我在CYGWIN环境中使用gcc 4.8.1编译器对此进行了测试 - 所以它也应该在*nix环境中工作.


Dou*_*der 48

要在运行时执行较少无用的代码,您可以查看:http: //www.stroustrup.com/bs_faq2.html#constraints ,它提供了一些有效执行编译时测试的类,并生成更好的错误消息.

特别是:

template<class T, class B> struct Derived_from {
        static void constraints(T* p) { B* pb = p; }
        Derived_from() { void(*p)(T*) = constraints; }
};

template<class T> void function() {
    Derived_from<T,Baseclass>();
}
Run Code Online (Sandbox Code Playgroud)

  • 确实,这是一个地狱般的答案!谢谢。提到的网站移至此处:http://www.stroustrup.com/bs_faq2.html#constraints (3认同)
  • 对我来说,这是最好,最有趣的答案。请务必查看Stroustrup的常见问题解答,以详细了解可以强制执行的各种约束。 (2认同)

sep*_*p2k 47

在这种情况下,你可以这样做:

template <class T> void function(){
    Baseclass *object = new T();

}
Run Code Online (Sandbox Code Playgroud)

如果T不是Baseclass的子类(或T Baseclass),则不会编译.

  • 它执行T()构造函数,并要求存在T()构造函数.请参阅我的答案,以避免这些要求. (7认同)
  • 很清晰,但是如果T是“重”类,这是一个问题。 (3认同)
  • @ phant0m:正确.您不能显式约束模板参数(除了使用概念,这些概念被考虑用于c ++ 0x但随后被删除).所有约束都是由您对其执行的操作隐式发生的(或者换句话说,唯一的约束是"类型必须支持对其执行的所有操作"). (2认同)

Dav*_*eas 11

您不需要概念,但可以使用SFINAE:

template <typename T>
boost::enable_if< boost::is_base_of<Base,T>::value >::type function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}
Run Code Online (Sandbox Code Playgroud)

请注意,这将仅在满足条件时实例化该函数,但如果不满足条件,则不会提供合理的错误.


jus*_*npc 11

从 C++11 开始,您不需要 Boost 或static_assert. C++11 引入了is_base_ofenable_if. C++14 引入了便利类型enable_if_t,但如果你被 C++11 卡住了,你可以简单地enable_if::type改用。

备选方案 1

David Rodríguez的解决方案可以改写如下:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of<Base, T>::value, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}
Run Code Online (Sandbox Code Playgroud)

备选方案 2

从 C++17 开始,我们有is_base_of_v. 该解决方案可以进一步重写为:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of_v<Base, T>, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}
Run Code Online (Sandbox Code Playgroud)

备选方案 3

您也可以只限制整个模板。您可以使用此方法来定义整个类。注意第二个参数是如何enable_if_t被删除的(之前它被设置为 void)。它的默认值实际上是void,但这并不重要,因为我们没有使用它。

#include <type_traits>

using namespace std;

template <typename T,
          typename = enable_if_t<is_base_of_v<Base, T>>>
void function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}
Run Code Online (Sandbox Code Playgroud)

从模板参数的文档中,我们看到这typename = enable_if_t...是一个名称为空的模板参数。我们只是使用它来确保类型的定义存在。特别是,enable_if_t如果Base不是 的基础,则不会被定义T

上述技术在 中作为示例给出enable_if

  • 如果可以将替代方案 3 写成如下,那不是很好吗?`模板 &lt;class T : Base&gt;` (3认同)

Ard*_*rda 10

从 C++20 开始,您可以约束模板参数。一种方法是使用条款requires。您可以在您的情况下按如下方式使用它(即确保T是特定类的子类Baseclass):

template <class T> void function()
   requires( std::is_base_of_v<Baseclass,T> )
{
   T *object = new T();
}
Run Code Online (Sandbox Code Playgroud)

或者,对于经常使用的约束,您可以使用命名概念,它允许更简洁的语法(归功于@sklott):

template <std::derived_from<Baseclass> T> void function()
{
   T *object = new T();
}
Run Code Online (Sandbox Code Playgroud)

使用约束比使用 a 更好static_assert,因为它们允许您拥有function具有不同约束的多个实现。例如,当T从不同的类派生时,您可以有另一个实现。如果需要,您还可以拥有一个根本不指定任何约束的默认实现,当没有任何受约束的实现符合条件时,由编译器选择。使用这些都是不可能的static_assert

附言。该requires子句非常适合临时约束,命名概念非常适合经常使用的约束(您还可以使用关键字创建自己的命名概念concept)。您可以在此处、关于std::is_base_of_v 此处以及关于std::derived_from 此处阅读有关约束和应用它们的替代方法的更多信息。对于更高级的用例,还请务必阅读requirements statements,不要将它们与requires子句混淆,即使它们共享相同的requires关键字。