tl;dr
相比static_assert
s,概念更强大,因为:
- 它们可以为您提供良好的诊断,这是您无法轻易实现的
static_asserts
- 它们可以让您轻松重载模板函数,而无需
std::enable_if
(这是不可能的,只有static_asserts
)
- 它们允许您定义静态接口并重用它们而不会丢失诊断(需要多个
static_asserts
在每个函数中)
- 它们可以让你更好地表达你的意图并提高可读性(这是模板的一个大问题)
这可以缓解以下问题:
并成为有趣范例的构建块。
什么是概念?
概念表示满足某些要求的类型的“类”(不是 C++ 术语,而是作为“组”)。作为示例,您可以看到Swappable http://en.cppreference.com/w/cpp/concept/Swappable概念表达了以下类型的集合:
你可以很容易地看到,例如,std::string
, std::vector
, std::deque
, int
等等...满足这个要求,因此可以在如下函数中互换使用:
template<typename Swappable>
void func(const Swappable& a, const Swappable& b) {
std::swap(a, b);
}
概念一直存在于C++中 http://en.cppreference.com/w/cpp/concept,在(可能不久)将来添加的实际功能将只允许您用语言表达和强制执行它们。
更好的诊断
就更好的诊断而言,我们现在只能相信委员会。但他们“保证”的输出:
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
非常有前途。
确实,您可以使用以下方法获得类似的输出static_assert
但这需要不同的static_assert
每个函数都需要一个 s,这可能很快就会变得乏味。
举个例子,假设您必须执行以下要求的数量:Container http://en.cppreference.com/w/cpp/concept/Container2 个采用模板参数的函数中的概念;您需要在两个函数中复制它们:
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
,但我们都知道这会变得多么丑陋。
举个例子,你可以有一个可以工作的函数Container
s 并用一个恰好可以更好地工作的版本来重载它SequenceContainer
s:
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();
}
很好,但是除非您想使用多重继承或基于实体组件的系统,否则您几乎只能为每个类提供一个可能的接口。
但如果你真的想要静态多态性(多态性不是that毕竟是动态的)你可以定义一个Object
需要的概念update
and draw
成员函数(可能还有其他函数)。
那时你可以创建一个自由函数:
template<Object O>
void process(O& o) {
o.update();
o.draw();
}
之后,您可以根据其他要求为游戏对象定义另一个接口。这种方法的优点在于您可以开发任意数量的接口,而无需
它们都在编译时进行检查和强制执行。
这只是一个愚蠢的例子(而且是一个非常简单的例子),但概念确实为 C++ 中的模板打开了一个全新的世界。
如果您想了解更多信息,您可以读这篇好文章 http://bartoszmilewski.com/2010/11/29/understanding-c-concepts-through-haskell-type-classes/关于 C++ 概念与 Haskell 类型类。