在Java中,您可以限制泛型,以便参数类型只是特定类的子类。这允许泛型知道该类型的可用函数。
我没有在带有模板的C ++中看到这个。那么有没有办法限制模板类型,如果没有,intellisense如何知道哪些方法可用于<typename T>
以及你的传入类型是否适用于模板化函数?
答案 0 :(得分:6)
从C ++ 11开始,无法约束模板类型参数。但是,您可以使用SFINAE来确保仅为特定类型实例化模板。请参阅std::enable_if
的示例。您需要将其与std::is_base_of
一起使用。
例如,要为特定派生类启用函数,您可以执行以下操作:
template <class T>
typename std::enable_if<std::is_base_of<Base, T>::value, ReturnType>::type
foo(T t)
{
// ...
}
C ++工作组(特别是Study Group 8)目前正在尝试向该语言添加概念和约束。这将允许您指定模板类型参数的要求。见the latest Concepts Lite proposal。正如Casey在评论中提到的,Concepts Lite将作为技术规范与C ++ 14同时发布。
答案 1 :(得分:4)
只是具体
#include <iostream>
#include <typeinfo>
template <typename T>
struct IsSpecific
{
void name() const { std::cout << typeid(T).name() << std::endl; }
};
template<typename T> struct Specific;
template <> struct Specific<char> : public IsSpecific<char>{};
template <> struct Specific<int> : public IsSpecific<int> {};
template <> struct Specific<long> : public IsSpecific<long>{};
int main() {
Specific<char>().name();
Specific<int>().name();
Specific<long>().name();
//Specific<std::string>().name(); // fails
}
答案 2 :(得分:4)
Java泛型和C ++模板是完全不同的东西,您可以做的最好的事情是避免尝试一对一映射。话虽这么说,问题仍然有效,答案并不简单。
大多数地方使用的简单方法是对类型的要求是模板上合同的一部分。例如,std::sort
要求前两个参数表现为 RandomAccessIterators ,这是仅文档接口(描述了属性,但代码中没有机制)为了这)。然后模板只使用该信息。
第二种最简单的方法是记录该合同并提供static_assert
来验证可以验证的内容。
下一个可用步骤是使用SFINAE,这是一种强制编译器检查要替换的类型的某些功能的技术。如果替换(和检查)失败,则模板将被删除为无效,编译器将继续运行。我个人不赞成SFINAE的大多数用途,它是一个很好的工具,但它经常被滥用。
在未来的标准中,将有更高级别的构造,通过 Concepts 在模板的参数上强制实施某种形式的接口。问题在于,已经证明很难定义如何定义,检测或验证这些约束,并且在找到一个好的解决方案之前,标准委员会不会满足于明显不好的方法。
所有这些,我可能想在这里回顾第一段。 Java泛型和C ++模板之间存在巨大差异。后者提供了一种称为编译时多态性的东西。在程序中的任何地方都没有定义真正的接口类型,[通常]没有约束条件,模板中使用的类型以任何可能的方式相关。
答案 3 :(得分:2)
将static_assert
与std::is_base_of
#include <type_traits>
class A {
};
class B: public A {
};
template <class T>
class Class1 {
static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A");
...
T foo();
...
};
Class1<A> a; //it works
Class1<B> b; //it works
Class1<int> i; //compile error
答案 4 :(得分:1)
在C ++中,如果你有一些模板参数T
,那么根据它的使用方式,它实际上被约束为一组类中的一个。例如,如果您在模板扩展中的某处引用T::foo
,则T
不可能是没有foo
成员的类。或者假设T::foo
确实存在,但类型错误;您的模板执行类似T::foo + 1
的操作,但T::foo
不存在算术。
如果T
以各种方式满足模板,并且生成的实例化有意义,则没有理由担心它。
C ++中的一个重要灵活性是能够使用模板与不以任何方式相关的类(即通过继承)。
需要使用模板的人必须编写一个结构特征与模板要求相匹配的类;模板的用户不必从某些类型派生,只是将它们用作参数。
这种限制的唯一好处是更清晰的诊断。您可能会收到“类型T::foo
与模板Widget
的参数2不匹配”,而不是以某种方式收到有关Xyz
不合适的错误消息。“
然而,虽然更清楚,但诊断的代价是接受这种不匹配实际上是真正问题的理念。 (如果程序员可以修复foo
成员并使其有效,该怎么办?)
答案 5 :(得分:1)
在C ++中执行此操作的方法是仅在类的输入和输出接口中使用模板类型。
struct base_pointer_container {
std::vector<Base*> data;
void append(Base* t) {
data.push_back(t);
}
T* operator[](std::size_t n) const {
Assert( n < data.size() );
return data[n];
}
};
template<typename T>
struct pointer_container:private base_pointer_container {
void append(Base* t) {
static_assert( std::is_base_of<Base,T>::value, "derivation failure" );
return base_pointer_container::append(t);
}
T* operator[](std::size_t n) const {
return static_cast<T*>(base_pointer_container::operator[](n));
}
};
这有点类似于Java的内容,如果我理解他们的泛型:Derived
版本只是围绕Base
版本的瘦包装器,它在输入/输出上进行类型转换操作
C ++ template
远不止于此。对于每组新的类型参数,整个类都是重新编写的,理论上,这些实现可以完全不同。 std::vector<int>
和std::vector<long>
之间没有任何关系 - 它们不相关class
es - 除了它们都可以模式匹配为std::vector
s,它们分享很多房产。
这种能力水平很少需要。但是对于它如何非常强大的示例,大多数标准库使用它来在std::function
中创建类型擦除对象。 std::function
可以使用任何定义operator()
且与其签名兼容的语言元素,无论运行时布局或类型的设计如何,并擦除(隐藏)其类型不同的事实
一个函数指针,一个lambda,一个程序员在西藏编写的仿函数 - 尽管没有运行时布局兼容性,std::function< bool() >
可以存储所有这些。
它(通常)通过为每个类型T
创建自定义持有者对象来实现此目的,该类型具有自己的副本,移动,构造,销毁和调用代码。其中每个都是针对相关类型自定义编译的。然后,它将对象的副本存储在自身中,并为每个操作公开virtual
接口。 std::function
然后保存一个指向该自定义对象持有者的父接口的指针,然后将其“类似值”的接口暴露给最终用户(您)。
pImpl
和template
鸭类型代码生成和template
构造函数的混合在C ++中称为类型擦除,它允许C ++不具有通用的“对象”根(对运行时对象布局有所限制)Java没有太多的牺牲方式。
答案 6 :(得分:0)
C ++采用的实际类型系统的演变正在进行中,它被命名为concepts
。
新的C ++ 1y概念可能会提供您正在寻找的内容,并且由于此功能已经计划用于C ++ 11但未进入最终草案,因此有一个gcc
fork实现这一概念。
对于现在的“穷人”解决方案,如果你想坚持使用标准给你的东西,就是使用type traits
。