将模板限制为仅限某些类?

时间:2013-12-18 22:12:17

标签: c++ templates c++11 template-meta-programming

在Java中,您可以限制泛型,以便参数类型只是特定类的子类。这允许泛型知道该类型的可用函数。

我没有在带有模板的C ++中看到这个。那么有没有办法限制模板类型,如果没有,intellisense如何知道哪些方法可用于<typename T>以及你的传入类型是否适用于模板化函数?

7 个答案:

答案 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_assertstd::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然后保存一个指向该自定义对象持有者的父接口的指针,然后将其“类似值”的接口暴露给最终用户(您)。

pImpltemplate鸭类型代码生成和template构造函数的混合在C ++中称为类型擦除,它允许C ++不具有通用的“对象”根(对运行时对象布局有所限制)Java没有太多的牺牲方式。

答案 6 :(得分:0)

C ++采用的实际类型系统的演变正在进行中,它被命名为concepts

新的C ++ 1y概念可能会提供您正在寻找的内容,并且由于此功能已经计划用于C ++ 11但未进入最终草案,因此有一个gcc fork实现这一概念。

对于现在的“穷人”解决方案,如果你想坚持使用标准给你的东西,就是使用type traits

相关问题