【问题标题】:Restrict C++ Template Parameter to Subclass将 C++ 模板参数限制为子类
【发布时间】:2011-03-11 15:40:25
【问题描述】:

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

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

}

【问题讨论】:

  • 你这样做是为了达到什么目的?
  • 我只是想确保 T 实际上是子类的实例或类本身。我提供的函数内的代码几乎无关紧要。
  • 相反,它非常相关。它决定了在该测试中投入工作是否是一个好主意。在许多(所有?)情况下,绝对不需要自己强制执行此类约束,而是让编译器在实例化时执行。例如,对于接受的答案,最好检查T 是否派生自Baseclass。到目前为止,该检查是隐式的,并且对重载决议不可见。但是,如果在任何地方都没有进行这种隐式约束,那么似乎没有理由进行人为限制。
  • 是的,我同意。但是,我只是想知道是否有办法做到这一点:) 但是,当然,您的观点非常有效,感谢您的洞察力。

标签: c++ templates


【解决方案1】:

使用兼容 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...
        ...
   }
}

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

【讨论】:

  • 对我来说它也可以这样工作:template&lt;class TEntity&gt; class BaseBiz { static_assert(std::is_base_of&lt;BaseEntity, TEntity&gt;::value, "TEntity not derived from BaseEntity"); ...
  • 我认为这是在运行时避免额外代码的最易读的答案。
【解决方案2】:

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

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

}

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

【讨论】:

  • 啊,是的,这是个好主意。谢谢!我接了那没办法在模板定义里定义?
  • @phant0m:正确。您不能显式约束模板参数(使用概念除外,这些概念曾被考虑用于 c++0x,但后来被删除)。所有约束都由您对其执行的操作隐含地发生(或者换句话说,唯一的约束是“该类型必须支持对其执行的所有操作”)。
  • 啊 ic。非常感谢您的澄清!
  • 执行 T() 构造函数,需要存在 T() 构造函数。有关避免这些要求的方法,请参阅我的答案。
  • 很好,很清楚,但是如果 T 是一个“重”类,这是一个问题。
【解决方案3】:

要在运行时执行较少无用的代码,您可以查看: 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>();
}

【讨论】:

  • 对我来说,这是最好和最有趣的答案。请务必查看 Stroustrup 的常见问题解答,以详细了解您可以与此类似地实施的各种约束。
  • 的确,这是一个地狱般的答案!谢谢。提到的网站移到这里:stroustrup.com/bs_faq2.html#constraints
  • 这是一个很好的答案。有什么好的方法可以避免unused variable 'p'unused variable 'pb' 的警告吗?
  • @FilipS。在B* pb = p; 之后添加(void)pb;
【解决方案4】:

从 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
}

备选方案 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
}

备选方案 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
}

从模板参数的documentation可以看出typename = enable_if_t...是一个空名的模板参数。我们只是使用它来确保类型的定义存在。特别是,如果Base 不是T 的基数,则不会定义enable_if_t

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

【讨论】:

  • 如果可以写成如下的替代方案3不是很好吗? template &lt;class T : Base&gt;
【解决方案5】:

您不需要概念,但可以使用 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
}

请注意,这只会在条件满足时实例化函数,但如果条件不满足,它不会提供明显的错误。

【讨论】:

  • 如果你把所有的函数都包装成这样呢?顺便说一句,它返回什么?
  • enable_if 采用第二个类型参数,默认为void。表达式enable_if&lt; true, int &gt;::type 表示类型int。我无法真正理解您的第一个问题是什么,您可以随心所欲地使用 SFINAE,但我不太明白您打算对所有功能做什么。
  • 对于那些想知道what SFINAE is的人:)
【解决方案6】:

你可以使用Boost Concept CheckBOOST_CONCEPT_REQUIRES

#include <boost/concept_check.hpp>
#include <boost/concept/requires.hpp>

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}

【讨论】:

    【解决方案7】:

    通过调用模板中存在于基类中的函数。

    如果您尝试使用无法访问此函数的类型实例化您的模板,您将收到编译时错误。

    【讨论】:

    • 这并不能确保T 是一个 BaseClass,因为BaseClass 中声明的成员可以在T 的声明中重复。
    猜你喜欢
    • 2022-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    相关资源
    最近更新 更多