在Java中,你可以定义泛型类,它只接受你选择的扩展类的类型,例如:
public class ObservableList<T extends List> {
...
}
这是使用“extends”关键字完成的。
在c++中是否有与此关键字简单等价的东西?
在Java中,你可以定义泛型类,它只接受你选择的扩展类的类型,例如:
public class ObservableList<T extends List> {
...
}
这是使用“extends”关键字完成的。
在c++中是否有与此关键字简单等价的东西?
当前回答
c++ 20概念使用示例
改编自https://en.cppreference.com/w/cpp/language/constraints,你可以只做一些鸭子打字:
#include <cassert>
#include <concepts>
struct ClassWithMyFunc {
int myFunc() {
return 1;
}
};
struct ClassWithoutMyFunc {};
// Concept HasMyFunc: type 'T' has `.myFunc` and
// its return is convertible to int.
template<typename T>
concept HasMyFunc= requires(T a) {
{ a.myFunc() } -> std::convertible_to<int>;
};
// Constrained function template
template<HasMyFunc T>
int f(T t) {
return t.myFunc() + 1;
}
int main() {
assert(f(ClassWithMyFunc()) == 2);
// assert(f(ClassWithoutMyFunc()) == 2);
}
编译并运行:
g++ -ggdb3 -O0 -std=c++20 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
如果取消注释// assert(f(ClassWithoutMyFunc()) == 2);,它会像预期的那样失败:
In file included from /usr/include/c++/10/cassert:44,
from main.cpp:1:
main.cpp: In function ‘int main()’:
main.cpp:27:34: error: use of function ‘int f(T) [with T = ClassWithoutMyFunc]’ with unsatisfied constraints
27 | assert(f(ClassWithoutMyFunc()) == 2);
| ^
main.cpp:21:5: note: declared here
21 | int f(T t) {
| ^
main.cpp:21:5: note: constraints not satisfied
main.cpp: In instantiation of ‘int f(T) [with T = ClassWithoutMyFunc]’:
main.cpp:27:5: required from here
main.cpp:15:9: required for the satisfaction of ‘HasMyFunc<T>’ [with T = ClassWithoutMyFunc]
main.cpp:15:20: in requirements with ‘T a’ [with T = ClassWithoutMyFunc]
main.cpp:16:15: note: the required expression ‘a.myFunc()’ is invalid
16 | { a.myFunc() } -> std::convertible_to<int>;
| ~~~~~~~~^~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
需要多个基类
如果你真的想要某个基类:
#include <concepts>
#include <type_traits>
struct Base1 {};
struct Base2 {};
struct Derived1 : public Base1 {};
struct Derived2 : public Base2 {};
struct NotDerived {};
template<typename T>
concept HasBase1Or2= std::is_base_of<Base1, T>::value || std::is_base_of<Base2, T>::value;
template<HasBase1Or2 T>
void f(T) {}
int main() {
f(Derived1());
f(Derived2());
// f(NotDerived());
}
如果取消注释// f(NotDerived());它在以下情况下失败:
main.cpp: In function ‘int main()’:
main.cpp:22:19: error: use of function ‘void f(T) [with T = NotDerived]’ with unsatisfied constraints
22 | f(NotDerived());
| ^
main.cpp:17:6: note: declared here
17 | void f(T) {}
| ^
main.cpp:17:6: note: constraints not satisfied
main.cpp: In instantiation of ‘void f(T) [with T = NotDerived]’:
main.cpp:22:19: required from here
main.cpp:13:9: required for the satisfaction of ‘HasBase1Or2<T>’ [with T = NotDerived]
main.cpp:13:55: note: no operand of the disjunction is satisfied
13 | concept HasBase1Or2= std::is_base_of<Base1, T>::value ||
| ~~~~~~^~
14 | std::is_base_of<Base2, T>::value;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
在Ubuntu 21.04 GCC 10.3.0上测试。
GCC 10似乎已经实现了它:https://gcc.gnu.org/gcc-10/changes.html,你可以在Ubuntu 20.04上获得它作为一个PPA。https://godbolt.org/ GCC 10.1无法在Ubuntu 20.04上识别概念。
其他回答
这在普通c++中是不可能的,但是你可以在编译时通过概念检查来验证模板参数,例如使用Boost的BCCL。
从c++ 20开始,概念开始成为该语言的官方特性。
好吧,你可以这样创建你的模板:
template<typename T>
class ObservableList {
std::list<T> contained_data;
};
然而,这将使限制成为隐式的,而且您不能只提供任何看起来像列表的东西。还有其他方法来限制所使用的容器类型,例如使用特定的迭代器类型,这些迭代器类型并不存在于所有容器中,但同样,这是一种隐式限制而不是显式限制。
据我所知,目前的标准中不存在完全镜像Java语句的结构。
有一些方法可以通过在模板中使用特定的typedefs来限制在模板中使用的类型。这将确保模板专门化的编译不包括特定的类型定义将失败,所以你可以有选择地支持/不支持某些类型。
在c++ 11中,概念的引入会让这变得更容易,但我不认为它会完全满足你的需求。
我建议使用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:使用声明但未定义的模板将导致链接器,而不是编译器,错误消息]
我认为之前所有的答案都是只见树木不见森林。
Java泛型与模板不同;它们使用类型擦除,这是一种动态技术,而不是编译时多态性,这是一种静态技术。这两种截然不同的策略不能很好地融合在一起的原因应该是显而易见的。
与其尝试使用编译时构造来模拟运行时构造,不如让我们看看extends实际上做了什么:根据Stack Overflow和Wikipedia, extends用于指示子类化。
c++也支持子类化。
还显示了一个容器类,它以泛型的形式使用类型擦除,并扩展以执行类型检查。在c++中,您必须自己执行类型擦除机制,这很简单:创建一个指向超类的指针。
让我们把它包装成一个类型定义,让它更容易使用,而不是一个完整的类,等等:
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示例,而不需要任何概念或模板专门化。由于虚拟表查找,它的执行速度也会像Java样式的泛型一样慢,但这通常是可以接受的损失。
这在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和类型特征。