仅接受某些types的C ++模板

在Java中,您可以定义只接受扩展您所选类的types的generics类,例如:

public class ObservableList<T extends List> { ... } 

这是使用“扩展”关键字完成的。

在C ++中有一些简单的等价于这个关键字吗?

我build议使用Boost的静态断言function与Boost Type Traits库中的is_base_of

 template<typename T> class ObservableList { BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator ... }; 

在其他一些简单的情况下,你可以简单地向前声明一个全局模板,但是只为有效的types定义(显式地或部分地专门化)它:

 template<typename T> class my_template; // Declare, but don't define // int is a valid type template<> class my_template<int> { ... }; // All pointer types are valid template<typename T> class my_template<T*> { ... }; // All other types are invalid, and will cause linker error messages. 

[小编EDIT 6/12/2013:使用声明但未定义的模板将导致链接器 ,而不是编译器,错误消息。]

这在C ++中通常是没有根据的,因为这里的其他答案已经注意到了。 在C ++中,我们倾向于定义基于其他约束的genericstypes,而不是“从这个类inheritance”。 如果你真的想这样做,那么在C ++ 11和<type_traits>执行起来很简单:

 #include <type_traits> template<typename T> class observable_list { static_assert(std::is_base_of<list, T>::value, "T must inherit from list"); // code here.. }; 

这打破了人们在C ++中期望的许多概念。 使用技巧来定义自己的特质会更好。 例如,也许observable_list想要接受任何types的容器,它的typedefs为const_iteratorbeginend成员函数为const_iterator 。 如果将其限制为从listinheritance的类,那么具有自己的types但不从listinheritance但提供这些成员函数和typedef的用户将无法使用您的observable_list

这个问题有两个解决scheme,其中之一是不限制任何东西,依靠鸭子打字。 对这个解决scheme的一个重要的认识是,它涉及到大量的错误,这对用户来说可能是困难的。 另一个解决scheme是定义特征来约束提供的types以满足接口要求。 这个解决scheme的重要意义在于涉及额外的写作,这可以被看作是烦人的。 然而,积极的一面是,你将能够写一个你自己的错误消息la static_assert

为了完整性,给出了上面例子的解决scheme:

 #include <type_traits> template<typename...> struct void_ { using type = void; }; template<typename... Args> using Void = typename void_<Args...>::type; template<typename T, typename = void> struct has_const_iterator : std::false_type {}; template<typename T> struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {}; struct has_begin_end_impl { template<typename T, typename Begin = decltype(std::declval<const T&>().begin()), typename End = decltype(std::declval<const T&>().end())> static std::true_type test(int); template<typename...> static std::false_type test(...); }; template<typename T> struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {}; template<typename T> class observable_list { static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef"); static_assert(has_begin_end<T>::value, "Must have begin and end member functions"); // code here... }; 

上面的例子中有很多概念展示了C ++ 11的特性。 一些好奇的search术语是可变模板,SFINAE,expression式SFINAE和types特征。

没有人提到的简单解决办法就是忽略这个问题。 如果我尝试使用一个int作为模板types在需要一个容器类,如向量或列表的函数模板,那么我会得到一个编译错误。 简单而粗糙,但它解决了这个问题。 编译器将尝试使用您指定的types,如果失败,则会生成编译错误。

唯一的问题是,你得到的错误消息将是棘手的阅读。 然而,这是一个非常普遍的做法。 标准库中充满了函数或类模板,这些函数或类模板期望来自模板types的特定行为,并且不检查所使用的types是否有效。

如果你想要更好的错误信息(或者如果你想捕捉不会产生编译器错误,但仍然没有意义的情况),你可以根据你想做的复杂程度,使用Boost的静态断言或者Boost concept_check库。

使用最新的编译器,您可以使用built_in static_assert ,而不是使用它。

我们可以使用std::is_base_ofstd::enable_if
static_assert可以被删除,上面的类可以自定义实现或者在boost中使用,如果我们不能引用type_traits

 #include <type_traits> #include <list> class Base {}; class Derived: public Base {}; #if 0 // wrapper template <class T> class MyClass /* where T:Base */ { private: static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base"); typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner; }; #elif 0 // base class template <class T> class MyClass: /* where T:Base */ protected std::enable_if<std::is_base_of<Base, T>::value, T>::type { private: static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base"); }; #elif 1 // list-of template <class T> class MyClass /* where T:list<Base> */ { static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base"); typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type; }; #endif int main() { #if 0 // wrapper or base-class MyClass<Derived> derived; MyClass<Base> base; // error: MyClass<int> wrong; #elif 1 // list-of MyClass<std::list<Derived>> derived; MyClass<std::list<Base>> base; // error: MyClass<std::list<int>> wrong; #endif // all of the static_asserts if not commented out // or "error: no type named 'type' in 'struct std::enable_if<false, ...>' pointing to: // 1. inner // 2. MyClass // 3. base + value_type } 

据我所知,这在C ++中目前是不可能的。 但是,有计划在新的C ++ 0x标准中添加一个名为“概念”的function,以提供您正在寻找的function。 这篇关于C ++概念的维基百科文章将更详细地解释它。

我知道这并不能解决你的直接问题,但是有一些C ++编译器已经开始添加新标准的function,所以有可能find一个已经实现了概念function的编译器。

只接受从Listtypes派生的typesT的等价物看起来像

 template<typename T, typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr> class ObservableList { // ... }; 

执行摘要:不要这样做。

j_random_hacker的答案告诉你如何做到这一点。 不过,我也想指出,你不应该这样做。 模板的要点是它们可以接受任何兼容的types,而Java风格types的约束则会破坏它。

Java的types约束是一个bug,而不是一个function。 他们在那里是因为Java在generics上键入了擦除,所以Java不能根据types参数的值单独调用方法。

另一方面C ++没有这样的限制。 模板参数types可以是与其使用的操作兼容的任何types。 没有必要有一个共同的基类。 这与Python的“Duck Typing”类似,但在编译时完成。

显示模板function的简单示例:

 // Sum a vector of some type. // Example: // int total = sum({1,2,3,4,5}); template <typename T> T sum(const vector<T>& vec) { T total = T(); for (const T& x : vec) { total += x; } return total; } 

这个求和函数可以和一个支持正确操作的任何types的向量求和。 它可以同时处理像int / long / float / double这样的基本types和用户定义的数字types,这些types会使+ =运算符重载。 哎呀,你甚至可以使用这个函数来连接string,因为它们支持+ =。

没有装箱/拆箱的基元是必要的。

请注意,它也使用T()构造T的新实例。 这在使用隐式接口的C ++中是微不足道的,但在types约束的Java中并不可行。

虽然C ++模板没有明确的types约束,但仍然是types安全的,并且不会使用不支持正确操作的代码进行编译。

我想所有先前的答案都没有看到树木的森林。

Javagenerics与模板不一样 , 他们使用types删除 ,这是一个dynamic的技术 ,而不是编译时间多态 ,这是静态技术 。 这应该是显而易见的,为什么这两种截然不同的策略不能很好地融合。

我们不是试图用一个编译时构造来模拟一个运行时的结构,而是看看实际上做了什么: 根据Stack Overflow和Wikipedia ,extends用于指示子类化。

C ++也支持子类化。

您还将显示一个容器类,它以通用forms使用types擦除,并进行扩展以执行types检查。 在C ++中,你必须自己完成types擦除机制,这很简单:创build一个指向超类的指针。

让我们把它包装成一个typedef,使其更容易使用,而不是整个class级,等等:

typedef std::list<superclass*> subclasses_of_superclass_only_list;

例如:

 class Shape { }; class Triangle : public Shape { }; typedef std::list<Shape*> only_shapes_list; only_shapes_list shapes; shapes.push_back(new Triangle()); // Works, triangle is kind of shape shapes.push_back(new int(30)); // Error, int's are not shapes 

现在,看来List是一个接口,代表一种集合。 C ++中的一个接口只是一个抽象类,也就是一个只能实现纯虚拟方法的类。 使用这种方法,你可以很容易地在C ++中实现你的java例子,而不需要任何的概念或模板专业化。 由于虚拟表查找,它的performance也会像Java风格的generics一样慢,但这通常是一个可以接受的损失。

这在纯C ++中是不可能的,但是你可以在编译时通过概念检查来validation模板参数,例如使用Boost的BCCL 。

 class Base { struct FooSecurity{}; }; template<class Type> class Foo { typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type; }; 

确保派生类inheritanceFooSecurity结构,编译器会在所有正确的位置感到不安。

在C ++中有一些简单的等价于这个关键字吗?

没有。

根据你想要完成的事情,可能会有足够(甚至更好)的替代品。

我已经浏览了一些STL代码(在linux上,我认为这是从SGI的实现派生的)。 它有“概念断言”; 例如,如果你需要一个能够理解*x++x ,那么概念断言就会把这个代码包含在一个无所事事的函数中(或类似的东西)。 这确实需要一些开销,所以把它放在一个定义取决于#ifdef debug的macros中可能是明智的。

如果子类关系真的是你想知道的,你可以在构造函数中声明T instanceof list (除了在C ++中“拼写”不同)。 这样,你可以testing出编译器的方式,而不能为你检查它。

这种types的检查没有关键字,但是你可以把一些代码放在那里,至less是有序的失败:

(1)如果你想要一个函数模板只接受某个基类X的参数,就把它分配给函数中的X引用。 (2)如果你想接受函数而不是基元,反之亦然,或者你想以其他方式过滤类,可以在你的函数中调用一个(空)模板帮助函数,该函数只为你想接受的类定义。

您也可以在类的成员函数中使用(1)和(2)来强制对整个类进行这些types的检查。

你可以把它放进一些聪明的macros来缓解你的痛苦。 🙂

那么,你可以创build你的模板,如下所示:

 template<typename T> class ObservableList { std::list<T> contained_data; }; 

这将会使隐含的限制,再加上你不能提供任何看起来像一个列表。 还有其他一些方法可以限制所使用的容器types,例如通过使用所有容器中不存在的特定迭代器types,但这又比隐式限制更隐含。

据我所知,目前的标准中并不存在将语句Java语句完全镜像的构造。

可以通过在模板中使用特定的typedef来限制可以在模板中使用的types。 这将确保不包含特定typedef的types的模板专门化编译将失败,因此您可以select性地支持/不支持某些types。

在C ++ 11中,概念的引入应该使这个更简单,但我不认为它会完全按照你想要的那样做。