c++ 概念中究竟有什么新东西?在我的理解中,它们在功能上等同于 using static_assert
,但以一种“很好”的方式意味着编译器错误将更具可读性(正如 Bjarne Stroustup 所说,你不会得到 10 页或错误,而只会得到一个)。
基本上,你可以用你也可以实现的概念做的所有事情是真的static_assert
吗?
有什么我想念的吗?
c++ 概念中究竟有什么新东西?在我的理解中,它们在功能上等同于 using static_assert
,但以一种“很好”的方式意味着编译器错误将更具可读性(正如 Bjarne Stroustup 所说,你不会得到 10 页或错误,而只会得到一个)。
基本上,你可以用你也可以实现的概念做的所有事情是真的static_assert
吗?
有什么我想念的吗?
与static_assert
s 相比,概念更强大,因为:
static_asserts
std::enable_if
(仅使用 是不可能的static_asserts
)static_asserts
它们允许您定义静态接口并重用它们而不会丢失诊断(每个函数中都需要多个)这可以简化以下世界:
并成为有趣范式的基石。
概念表示满足某些要求的类型的“类”(不是在 C++ 术语中,而是作为“组”)。作为示例,您可以看到该Swappable
概念表达了一组类型:
std::swap
您可以很容易地看到,例如 , std::string
, std::vector
, etc... 满足此要求std::deque
,int
因此可以在以下函数中互换使用:
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
动态的多态性),您可以定义一个需要update
和draw
成员函数(可能还有其他)的概念。
那时你可以创建一个自由函数:
template<Object O>
void process(O& o) {
o.update();
o.draw();
}
之后,您可以为具有其他要求的游戏对象定义另一个接口。这种方法的美妙之处在于,您可以根据需要开发任意数量的接口,而无需
并且它们都在编译时被检查和执行。
这只是一个愚蠢的例子(也是一个非常简单的例子),但概念确实为 C++ 中的模板打开了一个全新的世界。
如果您想了解更多信息,可以阅读这篇关于 C++ 概念与 Haskell 类型类的好文章。