c ++概念中有什么新东西?在我的理解中,它们在功能上等同于使用static_assert
,但是以“漂亮”的方式意味着编译器错误将更具可读性(因为Bjarne Stroustup说你不会得到10页或错误,而只是一个)。
基本上,您可以使用static_assert
实现的所有概念都可以实现吗?
我有什么遗失的吗?
答案 0 :(得分:19)
与static_assert
相比,概念更强大,因为:
static_asserts
std::enable_if
(仅static_asserts
不可能)static_asserts
)这可以缓解世界:
并成为有趣范例的构建模块。
概念表达满足特定要求的类型的“类”(不是在C ++术语中,而是作为“组”)。作为一个例子,您可以看到Swappable
概念表达了以下类型的集合:
std::swap
您可以很容易地看到,例如,std::string
,std::vector
,std::deque
,int
等......满足此要求,因此可以互换使用像这样的函数:
template<typename Swappable>
void func(const Swappable& a, const Swappable& b) {
std::swap(a, b);
}
概念always existed in 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
非常有前途。
确实,你可以使用static_assert
来获得类似的输出,但是每个函数需要不同的static_assert
s,这可能会非常繁琐。
作为一个例子,假设您必须在采用模板参数的2个函数中强制执行Container
概念给出的要求量;你需要在两个函数中复制它们:
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
的函数,并使用恰好与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();
}
很好,但除非您想使用多继承或基于实体组件的系统,否则每个类只有一个可能的接口。
但是如果你真的想要静态多态(多态不是那么动态)你可以定义一个Object
概念,需要update
}和draw
成员函数(可能还有其他函数)。
此时你可以创建一个免费功能:
template<Object O>
void process(O& o) {
o.update();
o.draw();
}
之后,您可以为游戏对象定义另一个具有其他要求的界面。这种方法的优点在于您可以根据需要开发任意数量的接口而无需
它们都在编译时检查并强制执行。
这只是一个愚蠢的例子(而且非常简单),但概念确实为C ++中的模板开辟了一个全新的世界。
如果你想要更多的信息,你可以{C}概念与Haskell类型类read this nice article。