79

在 C# 中,我们可以定义一个泛型类型,它对可用作泛型参数的类型施加约束。以下示例说明了通用约束的用法:

interface IFoo
{
}


class Foo<T> where T : IFoo
{
}

class Bar : IFoo
{
}

class Simpson
{
}

class Program
{
    static void Main(string[] args)
    {
        Foo<Bar> a = new Foo<Bar>();
        Foo<Simpson> b = new Foo<Simpson>(); // error CS0309
    }
}

有没有一种方法可以在 C++ 中对模板参数施加约束。


C++0x 对此有本机支持,但我说的是当前的标准 C++。

4

10 回答 10

77

如果你使用 C++11,你可以使用static_assertwithstd::is_base_of来达到这个目的。

例如,

#include <type_traits>

template<typename T>
class YourClass {

    YourClass() {
        // Compile-time check
        static_assert(std::is_base_of<BaseClass, T>::value, "type parameter of this class must derive from BaseClass");

        // ...
    }
}
于 2014-04-08T11:00:21.493 回答
44

“隐式”是正确答案。由于它们的编译方式,模板有效地创建了“鸭子打字”场景。您可以在模板类型的值上调用您想要的任何函数,并且唯一将被接受的实例是为其定义了该方法的实例。例如:

template <class T>
int compute_length(T *value)
{
    return value->length();
}

我们可以在指向任何声明该方法的类型的指针上调用此方法length()以返回一个int. 因此:

string s = "test";
vector<int> vec;
int i = 0;

compute_length(&s);
compute_length(&vec);

...但不是指向声明的类型的指针length()

compute_length(&i);

这第三个示例将无法编译。

这是因为 C++ 为每个实例化编译模板化函数(或类)的新版本。当它执行该编译时,它会在类型检查之前将模板实例化直接、几乎类似于宏的替换到代码中。如果一切仍然适用于该模板,则编译继续进行,我们最终会得到结果。如果有任何事情失败(比如int*没有声明length()),那么我们会得到可怕的六页模板编译时错误。

于 2008-09-23T17:15:53.987 回答
36

正如其他人所提到的,C++0x 正在将其内置到语言中。在那之前,我会推荐Bjarne Stroustrup关于模板约束的建议。

编辑:Boost也有自己的替代方案

Edit2:看起来概念已从 C++0x 中删除

于 2008-09-23T17:12:18.210 回答
18

您可以在 IFoo 上放置一个不执行任何操作的保护类型,确保它在 Foo 中的 T 上:

class IFoo
{
public:
    typedef int IsDerivedFromIFoo;
};

template <typename T>
class Foo<T>
{
    typedef typename T::IsDerivedFromIFoo IFooGuard;
}
于 2008-09-23T17:19:55.913 回答
9

查看升压

Boost 概念检查库 (BCCL)

概念检查库允许以提议的 C++ 语言扩展的风格添加显式声明和概念检查。

于 2008-09-23T17:08:13.257 回答
2

有点。如果您将 static_cast 转换为 IFoo*,则无法实例化模板,除非调用者传递一个可以分配给 IFoo * 的类。

于 2008-09-23T17:09:18.700 回答
2

你能行的。创建基础模板。使其只有 Private 构造函数。然后为您想要允许的每种情况创建专业化(或者如果不允许的列表远小于允许的列表,则进行相反的处理)。

编译器将不允许您实例化使用带有私有构造函数的版本的模板。

此示例仅允许使用 int 和 float 进行实例化。

template<class t> class FOO { private: FOO(){}};

template<> class FOO<int>{public: FOO(){}};

template<> class FOO<float>{public: FOO(){}};

它不是一种简短而优雅的方式,但它是可能的。

于 2008-09-30T12:03:58.163 回答
1

只是隐含的。
您在实际调用的方法中使用的任何方法都被强加在模板参数上。

于 2008-09-23T17:07:24.397 回答
1

使用 C++20,是的,有:约束和概念

也许你想保证一个模板是从一个特定的类派生的:

#include <concepts>

template<class T, class U>
concept Derived = std::is_base_of<U, T>::value;

class ABase { };
class ADerived : ABase { };

template<Derived<ABase> T>
class AClass {
    T aMemberDerivedFromABase;
};

然后像往常一样编译以下内容:

int main () {
    AClass<ADerived> aClass;
    return 0;
}

但是现在当你做一些违背约束的事情时:

class AnotherClass {

};
int main () {
    AClass<AnotherClass> aClass;
    return 0;
}

AnotherClass 不是从 ABase 派生的,因此我的编译器 (GCC) 大致给出以下错误:

在函数'int main()'中:注意:不满足约束注意:表达式'std::is_base_of<U, T>::value [with U = ABase; T = AnotherClass]' 评估为'假' 9 | 概念派生 = std::is_base_of<U, T>::value;

正如您可以想象的那样,此功能非常有用,并且可以做的不仅仅是将类限制为具有特定的基础。

于 2022-02-13T12:41:29.493 回答
-1

查看 CRTP 模式(Curiously Recursive Template Pattern)。它旨在帮助支持静态继承。

于 2008-12-04T18:33:55.683 回答