在Java中,你可以定义泛型类,它只接受你选择的扩展类的类型,例如:
public class ObservableList<T extends List> {
...
}
这是使用“extends”关键字完成的。
在c++中是否有与此关键字简单等价的东西?
在Java中,你可以定义泛型类,它只接受你选择的扩展类的类型,例如:
public class ObservableList<T extends List> {
...
}
这是使用“extends”关键字完成的。
在c++中是否有与此关键字简单等价的东西?
当前回答
执行摘要:不要那样做。
J_random_hacker的答案告诉您如何做到这一点。然而,我也想指出,你不应该这样做。模板的全部意义在于它们可以接受任何兼容的类型,而Java样式类型约束打破了这一点。
Java的类型约束是一个错误,而不是一个特性。它们存在的原因是Java对泛型进行了类型擦除,因此Java无法弄清楚如何仅根据类型参数的值调用方法。
另一方面,c++没有这样的限制。模板形参类型可以是与使用它们的操作兼容的任何类型。不需要有一个公共基类。这类似于Python的“Duck Typing”,但在编译时完成。
一个简单的例子展示了模板的力量:
// 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;
}
这个求和函数可以对支持正确运算的任何类型的向量求和。它既适用于int/long/float/double等原语,也适用于重载+=操作符的用户定义数字类型。你甚至可以使用这个函数来连接字符串,因为它们支持+=。
不需要对基本类型进行装箱/解装箱。
注意,它还使用T()构造T的新实例。这在使用隐式接口的c++中是微不足道的,但在使用类型约束的Java中是不可能的。
虽然c++模板没有显式的类型约束,但它们仍然是类型安全的,并且不会使用不支持正确操作的代码进行编译。
其他回答
在c++中是否有与此关键字简单等价的东西?
No.
根据你想要实现的目标,可能会有足够的(甚至更好的)替代品。
我已经看了一些STL代码(在linux上,我认为它是从SGI的实现派生出来的)。它有“概念断言”;例如,如果您需要一个能够理解*x和++x的类型,则概念断言将在一个什么都不做的函数(或类似的东西)中包含该代码。它确实需要一些开销,所以把它放在一个定义依赖于#ifdef debug的宏中可能是聪明的。
如果子类关系确实是你想知道的,你可以在构造函数中断言T instanceof list(除非它在c++中“拼写”不同)。这样,你就可以通过测试来避免编译器无法为你检查它。
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;
};
确保派生类继承了FooSecurity结构,编译器就会在所有正确的地方出错。
只接受类型List派生的类型T的等效函数如下所示
template<typename T,
typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
// ...
};
这在c++中通常是不合理的,正如这里的其他答案所指出的那样。在c++中,我们倾向于基于其他约束来定义泛型类型,而不是“从该类继承”。如果你真的想这样做,在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..
};
This breaks a lot of the concepts that people expect in C++ though. It's better to use tricks like defining your own traits. For example, maybe observable_list wants to accept any type of container that has the typedefs const_iterator and a begin and end member function that returns const_iterator. If you restrict this to classes that inherit from list then a user who has their own type that doesn't inherit from list but provides these member functions and typedefs would be unable to use your observable_list.
There are two solutions to this issue, one of them is to not constrain anything and rely on duck typing. A big con to this solution is that it involves a massive amount of errors that can be hard for users to grok. Another solution is to define traits to constrain the type provided to meet the interface requirements. The big con for this solution is that involves extra writing which can be seen as annoying. However, the positive side is that you will be able to write your own error messages a la static_assert.
为了完整起见,上面例子的解决方案如下:
#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的特性。对于好奇的人来说,一些搜索词是可变参数模板、SFINAE、表达式SFINAE和类型特征。
我建议使用Boost的静态断言特性与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
...
};
在其他一些更简单的情况下,你可以简单地前向声明一个全局模板,但只为有效类型定义(显式或部分专门化):
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.
[Minor EDIT 6/12/2013:使用声明但未定义的模板将导致链接器,而不是编译器,错误消息]