在Java中,你可以定义泛型类,它只接受你选择的扩展类的类型,例如:

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

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

在c++中是否有与此关键字简单等价的东西?


当前回答

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

从c++ 20开始,概念开始成为该语言的官方特性。

其他回答

我认为之前所有的答案都是只见树木不见森林。

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++中是否有与此关键字简单等价的东西?

No.

根据你想要实现的目标,可能会有足够的(甚至更好的)替代品。

我已经看了一些STL代码(在linux上,我认为它是从SGI的实现派生出来的)。它有“概念断言”;例如,如果您需要一个能够理解*x和++x的类型,则概念断言将在一个什么都不做的函数(或类似的东西)中包含该代码。它确实需要一些开销,所以把它放在一个定义依赖于#ifdef debug的宏中可能是聪明的。

如果子类关系确实是你想知道的,你可以在构造函数中断言T instanceof list(除非它在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++ 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上识别概念。

我建议使用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:使用声明但未定义的模板将导致链接器,而不是编译器,错误消息]