13

c++ 概念中究竟有什么新东西?在我的理解中,它们在功能上等同于 using static_assert,但以一种“很好”的方式意味着编译器错误将更具可读性(正如 Bjarne Stroustup 所说,你不会得到 10 页或错误,而只会得到一个)。

基本上,你可以用你也可以实现的概念做的所有事情是真的static_assert吗?

有什么我想念的吗?

4

1 回答 1

23

tl;博士

static_asserts 相比,概念更强大,因为:

  • 它们为您提供了您无法轻易实现的良好诊断static_asserts
  • 它们让您可以轻松地重载模板函数,而无需std::enable_if(仅使用 是不可能的static_asserts
  • static_asserts它们允许您定义静态接口并重用它们而不会丢失诊断(每个函数中都需要多个)
  • 它们让您更好地表达您的意图并提高可读性(这是模板的一个大问题)

这可以简化以下世界:

  • 模板
  • 静态多态性
  • 重载

并成为有趣范式的基石。


什么是概念?

概念表示满足某些要求的类型的“类”(不是在 C++ 术语中,而是作为“组”)。作为示例,您可以看到该Swappable概念表达了一组类型:

  • 允许调用std::swap

您可以很容易地看到,例如 , std::string, std::vector, etc... 满足此要求std::dequeint因此可以在以下函数中互换使用:

template<typename Swappable>
void func(const Swappable& a, const Swappable& b) {
    std::swap(a, b);
}

概念始终存在于 C++中,将在(可能不久的)将来添加的实际功能将只允许您用该语言表达和实施它们。


更好的诊断

就更好的诊断而言,我们现在只需要相信委员会。但是他们“保证”的输出:

error: no matching function for call to 'sort(list<int>&)'
sort(l); 
      ^
note: template constraints not satisfied because 
note: `T' is not a/an `Sortable' type [with T = list<int>] since
note: `declval<T>()[n]' is not valid syntax

很有前途。

确实,您可以使用 s 实现类似的输出,static_assert但这需要static_assert每个函数使用不同的 s,并且很快就会变得乏味。

例如,假设您必须Container在 2 个采用模板参数的函数中强制执行概念给出的要求数量;您需要在两个函数中复制它们:

template<typename C>
void func_a(...) {
    static_assert(...);
    static_assert(...);
    // ...
}

template<typename C>
void func_b(...) {
    static_assert(...);
    static_assert(...);
    // ...
}

否则,您将失去区分未满足哪个要求的能力。

相反,使用概念,您可以定义概念并通过简单地编写来强制执行它:

template<Container C>
void func_a(...);

template<Container C>
void func_b(...);

概念重载

引入的另一个重要特性是能够在模板约束上重载模板函数。是的,这也是可能的std::enable_if,但我们都知道这会变得多么丑陋。

举个例子,你可以有一个在 s 上工作的函数,Container并用一个恰好在 s 上工作得更好的版本重载它SequenceContainer

template<Container C>
int func(C& c);

template<SequenceContainer C>
int func(C& c);

没有概念的替代方案是:

template<typename T>
std::enable_if<
    Container<T>::value,
    int
> func(T& c);

template<typename T>
std::enable_if<
    SequenceContainer<T>::value,
    int
> func(T& c);

绝对更丑陋,可能更容易出错。


更简洁的语法

正如您在上面的示例中看到的那样,语法绝对更简洁,更直观的概念。这可以减少表达约束所需的代码量,并可以提高可读性。

如前所述,您实际上可以通过以下方式达到可接受的水平:

static_assert(Concept<T>::value);

但那时你会失去对不同的伟大诊断static_assert。有了概念,您就不需要这种权衡。


静态多态性

最后,概念与 Haskell 中的类型类等其他功能范式有有趣的相似之处。例如,它们可用于定义静态接口

例如,让我们考虑一个(臭名昭著的)游戏对象接口的经典方法:

struct Object {
    // …
    virtual update() = 0;
    virtual draw() = 0;
    virtual ~Object();
};

然后,假设你有一个多态std::vector的派生对象,你可以这样做:

for (auto& o : objects) { 
    o.update();
    o.draw();
}

很好,但是除非您想使用多重继承或基于实体组件的系统,否则您几乎会被每个类只有一个可能的接口所困。

但是,如果您确实想要静态多态性(毕竟不是那么Object动态的多态性),您可以定义一个需要updatedraw成员函数(可能还有其他)的概念。

那时你可以创建一个自由函数:

template<Object O>
void process(O& o) {
    o.update();
    o.draw();
}

之后,您可以为具有其他要求的游戏对象定义另一个接口。这种方法的美妙之处在于,您可以根据需要开发任意数量的接口,而无需

  • 修改你的课程
  • 需要一个基类

并且它们都在编译时被检查和执行。

这只是一个愚蠢的例子(也是一个非常简单的例子),但概念确实为 C++ 中的模板打开了一个全新的世界。

如果您想了解更多信息,可以阅读这篇关于 C++ 概念与 Haskell 类型类的好文章。

于 2014-04-15T22:12:13.987 回答