C++ 相当于对 java parameter/return 类型使用 <T extends Class>
C++ equivalent of using <T extends Class> for a java parameter/return type
在 java 中,要创建一个 returns 与参数类型相同并扩展某个 class 的对象的函数,我将键入:
public <T extends MyClass> T foo(T bar) {...}
是否有与此等效的 C++?
换句话说,我如何制作一个函数,它接受任何 class 扩展某个 class 和相同类型的 returns? (这是为了 abstract/pure 虚拟 classes 的目的)。
如果您有可用的 C++11 或更高版本,我们可以在此处使用 enable_if
template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
return T();
}
例如:
class MyClass
{
public:
int a = 1;
};
class Derived : public MyClass
{
public:
int b = 2;
};
class NotDerived
{
public:
int b = 3;
};
template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
return T();
}
int main()
{
Derived d;
NotDerived nd;
std::cout << Foo(d).b << std::endl;; // works
//std::cout << (Foo(nd)).b << std::endl;; //compiler error
return 0;
}
由于我无法对已接受的答案发表评论,因此我提供了一个以此为基础的新答案。
可以通过将 enable_if
条件变为 default type template parameter 而不是 nullptr
来简化模板参数。
template<typename T, typename = std::enable_if<std::is_base_of<MyClass, T>::value>>
从技术上讲,正如其他答案所示,有一些方法可以在编译时将其限制为特定类型的子类型。然而,大多数时候,你会做
template <typename T> T foo(T bar) {...}
无需指定界限。
在 Java 中,泛型需要边界,因为泛型 class 或方法是与它的任何使用分开编译的。泛型 classes 或方法被编译一次,成为字节码中的单一版本,单一版本能够处理调用者抛给它的任何满足其声明范围的参数。
编译器必须在方法主体中对类型 T
的使用进行类型检查,例如方法调用、字段访问等,而不知道 T
是什么,因此您必须提供一个界限,这样编译器就可以满足,例如一个方法调用是有效的,因为它是在满足该界限的所有类型上定义的。例如,如果方法主体中有表达式 bar.baz()
,编译器只会在类型 MyClass
(以及它的所有子类型)提供方法 .baz()
;如果您没有提供边界,编译器会抱怨 Object
(隐式上限)没有方法 .baz()
.
C++ 模板不同。模板化的 class 或函数是 "instantiated" (再次编译)用于每个不同类型的参数。因此,在为特定 T
编译函数体时,编译器知道 T
是什么,并且能够直接对该类型的使用进行类型检查。
所以如果你在函数体中有表达式 bar.baz()
,那就没问题了。如果你使用此函数时 T
是一个扩展 MyClass
的类型,那么它将编译正常,因为这样的类型有一个 .baz()
。如果您将此函数与没有 .baz()
的类型一起使用,那么它将无法在该用法下进行编译。如果你不小心使用了一个类型没有扩展 MyClass
但有一个 .baz()
的函数,其参数类型和 return 类型与你使用它的方式相匹配,它也会编译;但这不一定是坏事。 C++ 模板通常不与类型层次结构一起使用,而是与类型需要提供的要求一起使用。因此,例如,排序算法不会要求其容器 and/or 元素类型扩展某种类型,而是容器提供某些功能(例如随机访问下标运算符),并且元素类型提供某些功能(例如小于运算符)。
也想知道同样的问题,那是通过概念的C++20方法。
#include <iostream>
#include <utility>
#include <concepts>
using namespace std;
class MyClass
{
public:
inline virtual void print() const noexcept { cout << "Myclass" << endl; }
};
class Derived : public MyClass
{
public:
inline virtual void print() const noexcept override { cout << "Derived" << endl; }
};
class NotDerived
{};
创建一个名称为 CheckType 的概念并满足
当概念的参数 Type 是 BaseClass 或 DerivedClass : BaseClass.
template <class Type, class BaseClass>
concept CheckType = std::is_base_of<BaseClass, Type>::value;
创建一个泛型 class,其中 T 类型需要是 MyClass 或 Derived : MyClass 类型。
template <class T>
requires CheckType<T, MyClass>
class OnlyMyClass
{
private:
T instance;
public:
inline void print() const noexcept { instance.print(); } // Print
};
int main()
{
//OnlyMyClass<NotDerived> o1; // error, the associated constraints are not satisfied.
OnlyMyClass<MyClass> o2; // nice!
OnlyMyClass<Derived> o3; // nice!
o2.print();
o3.print();
return 0;
} // main
在 java 中,要创建一个 returns 与参数类型相同并扩展某个 class 的对象的函数,我将键入:
public <T extends MyClass> T foo(T bar) {...}
是否有与此等效的 C++?
换句话说,我如何制作一个函数,它接受任何 class 扩展某个 class 和相同类型的 returns? (这是为了 abstract/pure 虚拟 classes 的目的)。
如果您有可用的 C++11 或更高版本,我们可以在此处使用 enable_if
template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
return T();
}
例如:
class MyClass
{
public:
int a = 1;
};
class Derived : public MyClass
{
public:
int b = 2;
};
class NotDerived
{
public:
int b = 3;
};
template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
return T();
}
int main()
{
Derived d;
NotDerived nd;
std::cout << Foo(d).b << std::endl;; // works
//std::cout << (Foo(nd)).b << std::endl;; //compiler error
return 0;
}
由于我无法对已接受的答案发表评论,因此我提供了一个以此为基础的新答案。
可以通过将 enable_if
条件变为 default type template parameter 而不是 nullptr
来简化模板参数。
template<typename T, typename = std::enable_if<std::is_base_of<MyClass, T>::value>>
从技术上讲,正如其他答案所示,有一些方法可以在编译时将其限制为特定类型的子类型。然而,大多数时候,你会做
template <typename T> T foo(T bar) {...}
无需指定界限。
在 Java 中,泛型需要边界,因为泛型 class 或方法是与它的任何使用分开编译的。泛型 classes 或方法被编译一次,成为字节码中的单一版本,单一版本能够处理调用者抛给它的任何满足其声明范围的参数。
编译器必须在方法主体中对类型 T
的使用进行类型检查,例如方法调用、字段访问等,而不知道 T
是什么,因此您必须提供一个界限,这样编译器就可以满足,例如一个方法调用是有效的,因为它是在满足该界限的所有类型上定义的。例如,如果方法主体中有表达式 bar.baz()
,编译器只会在类型 MyClass
(以及它的所有子类型)提供方法 .baz()
;如果您没有提供边界,编译器会抱怨 Object
(隐式上限)没有方法 .baz()
.
C++ 模板不同。模板化的 class 或函数是 "instantiated" (再次编译)用于每个不同类型的参数。因此,在为特定 T
编译函数体时,编译器知道 T
是什么,并且能够直接对该类型的使用进行类型检查。
所以如果你在函数体中有表达式 bar.baz()
,那就没问题了。如果你使用此函数时 T
是一个扩展 MyClass
的类型,那么它将编译正常,因为这样的类型有一个 .baz()
。如果您将此函数与没有 .baz()
的类型一起使用,那么它将无法在该用法下进行编译。如果你不小心使用了一个类型没有扩展 MyClass
但有一个 .baz()
的函数,其参数类型和 return 类型与你使用它的方式相匹配,它也会编译;但这不一定是坏事。 C++ 模板通常不与类型层次结构一起使用,而是与类型需要提供的要求一起使用。因此,例如,排序算法不会要求其容器 and/or 元素类型扩展某种类型,而是容器提供某些功能(例如随机访问下标运算符),并且元素类型提供某些功能(例如小于运算符)。
也想知道同样的问题,那是通过概念的C++20方法。
#include <iostream>
#include <utility>
#include <concepts>
using namespace std;
class MyClass
{
public:
inline virtual void print() const noexcept { cout << "Myclass" << endl; }
};
class Derived : public MyClass
{
public:
inline virtual void print() const noexcept override { cout << "Derived" << endl; }
};
class NotDerived
{};
创建一个名称为 CheckType 的概念并满足 当概念的参数 Type 是 BaseClass 或 DerivedClass : BaseClass.
template <class Type, class BaseClass>
concept CheckType = std::is_base_of<BaseClass, Type>::value;
创建一个泛型 class,其中 T 类型需要是 MyClass 或 Derived : MyClass 类型。
template <class T>
requires CheckType<T, MyClass>
class OnlyMyClass
{
private:
T instance;
public:
inline void print() const noexcept { instance.print(); } // Print
};
int main()
{
//OnlyMyClass<NotDerived> o1; // error, the associated constraints are not satisfied.
OnlyMyClass<MyClass> o2; // nice!
OnlyMyClass<Derived> o3; // nice!
o2.print();
o3.print();
return 0;
} // main