在 C++ 中,根据 此来源 假设模板参数具有某些字段或方法是合法的。如果没有,它就会编译失败。
template<class Container>
void draw_all(Container& c)
{
for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}
在此示例中,
Container
必须实现begin()
和end()
才能编译程序。然而,看看函数的原型,似乎没有明显的限制。
换句话说,有关接口使用的信息与实现交织在一起(即使使用
static_assert
)。
对我来说,这似乎是一种不好的做法(尽管我自己也遇到过它仍然是我能找到的最优雅的解决方案的情况)。是吗?如果不是,使用它的理由是什么?
在 C++20 之前,这就是我们所拥有的一切。您无法从 C++20 之前的 C++ 模板中夺走的一件事是,它们允许极大的灵活性(在某些情况下可能需要),并且在某种程度上允许 API 提供者和 API 使用者之间的隐式契约。
C++20 出现并引入了概念,几乎从字面上看是为了解决由于所述合同过于宽松而引起的问题。
现在,该函数可能会写成:
template <class Container>
concept DrawableContainer = requires (Container c) {
{ c.begin() };
{ c.end() };
{ *(c.begin()) } -> std::convertible_to<typename Container::value_type>;
requires std::invocable<decltype(&Shape::draw), decltype(*(c.begin()))>;
// Plus whatever else constraints are needed
};
void draw_all(DrawableContainer auto& c)
{
for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}
这仍然允许相同的灵活性,但具有即使仅使用代码也可以更好地记录的附加功能。
如果您无法了解概念,那么没有比好的旧模板更好的解决方案了。您可以尝试在任何地方使用SFINAE,以使其更容易使用。