将模板限制为仅某些类

Restricting templates to only certain classes?

本文关键字:      更新时间:2023-10-16

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

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

从 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++工作组(特别是第8研究组)目前正在尝试在该语言中增加概念和限制。这将允许您指定模板类型参数的要求。请参阅最新的概念精简版提案。正如Casey在评论中提到的,Concepts Lite将与C++14同时作为技术规范发布。

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

Java 泛型和C++模板是完全不同的东西,你能做的最好的事情就是避免尝试一对一映射。话虽如此,这个问题仍然有效,答案并不简单。

大多数地方使用的简单方法是,对类型的要求是模板上协定的一部分。例如,std::sort要求前两个参数表现为 RandomAccessIterators,这是一个仅文档接口(描述了属性,但代码中没有用于此的机制)。然后模板只使用该信息。

第二种最简单的方法是记录该合同并提供验证可以验证的内容的static_assert

下一个可用步骤是使用 SFINAE,这是一种强制编译器检查要替换的类型的某些功能的技术。如果替换(和检查)失败,则模板将被删除为无效,编译器将继续。我个人不鼓励使用SFINAE,这是一个很好的工具,但它经常被滥用。

在未来的标准中,将有更高级别的构造,通过概念在模板的参数上强制执行某种形式的接口。问题在于,事实证明很难定义如何定义、检测或验证这些约束,在找到一个好的解决方案之前,标准委员会不会满足于一个明知是坏的方法。

话虽如此,我可能想在这里回顾第一段。Java 泛型和C++模板之间存在巨大差异。后者提供了称为编译时多态性的东西。程序中的任何地方都没有定义真正的接口类型,[通常]没有约束模板中使用的类型以任何可能的方式相关。

只要具体一点

#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
}

在C++中,如果你有一些模板参数T,那么由于它的使用方式,它实际上被约束为一组类之一。例如,如果您在模板扩展中的某处引用T::foo,则T不可能是没有foo成员的类。 或者假设T::foo确实存在,但类型错误;您的模板执行类似 T::foo + 1 ,但那里的T::foo不是算术。

如果T各方面都满足模板,并且生成的实例化有意义,则没有理由担心它。

C++能够将

模板与没有任何关系(即通过继承)相关的类一起使用是一种重要的灵活性。

需要使用模板的人只需要编写一个结构特征与模板要求匹配的类;模板的用户不必仅仅为了将它们用作参数而从某些类型派生。

这种约束的唯一好处是更清晰的诊断。您可能会收到"类型 Widget 与模板 Xyz 的参数 2 不匹配",而不是收到有关T::foo以某种方式不合适的错误消息。

然而,尽管更清晰,但诊断是以接受这种不匹配实际上是真正的问题的哲学为代价的。(如果程序员可以修复foo成员并使其工作怎么办?

在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进行模式匹配,并且它们共享许多属性。

这种级别的功率很少需要。 但是对于它如何非常强大的示例,大多数标准库使用它来创建 std::function 中的类型擦除对象。 std::function可以采用定义operator()并与其签名兼容的任何语言元素,而不考虑类型的运行时布局或设计,并擦除(隐藏)其类型不同的事实。

所以一个函数指针,一个lambda,一个由西藏的程序员编写的函子——尽管没有运行时布局兼容性,std::function< bool() >可以存储所有这些。

它(通常)通过为每个类型T创建自定义 holder 对象来实现这一点,该对象具有自己的复制、移动、构造、销毁和调用代码。 其中每个都是针对相关类型自定义编译的。 然后,它将对象的副本存储在自身中,并向其中每个操作公开一个virtual接口。 然后,std::function持有指向该自定义对象持有者的父接口的指针,然后向最终用户(您)公开其"类似值"的接口。

这种pImpltemplate鸭子类型代码生成和template构造函数的混合在C++中被称为类型擦除,它允许C++没有Java具有的公共"对象"根(对运行时对象布局的必然限制),而无需牺牲太多。

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

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

目前,"穷人"的解决方案,如果你想坚持标准给你的东西,就是使用type traits

相关文章:
  • 没有找到相关文章