C++ 20个概念真正解决了什么问题?

发布于 2025-01-28 19:55:41 字数 1556 浏览 3 评论 0原文

我试图了解C ++ 20个概念正在解决哪些问题,以及它们对最终用户的准确有用。我知道它可以帮助编译器解决函数调用,但是从我对其的粗略阅读来看,从最终用户的角度来看,它似乎并没有增加太多。

考虑以下代码:

void addToCollection(auto& collection, const auto& val) {
    collection.push_back(val);
}

int main() {
    vector<int> fooVec;
    addToCollection(fooVec, 15);
    
    unordered_set<int> fooSet;
    addToCollection(fooSet, 15);

    return 0;
}

编译器发出的一个非常明显的消息告诉我们函数addTocollectionUnordered_set不兼容。

error: no member named 'push_back' in 'std::unordered_set<int>'
    collection.push_back(val);
    ~~~~~~~~~~ ^
concept.cpp:34:5: note: in instantiation of function template specialization 'addToCollection<std::unordered_set<int>, int>'
      requested here
    addToCollection(fooSet, 15);

在概念之前,您将通过提供明确的模板专业化来解决这样的失败。

template<>
void addToCollection(unordered_set<int>& collection, const auto& val) {
    collection.insert(val);
}

使用概念,看来解决此问题的正确方法是通过对功能参数类型的约束来约束。

template<typename CollectionType>
concept HasPushBack = requires(CollectionType collection, typename CollectionType::value_type val) {
    collection.push_back(val);
};


void addToCollection(HasPushBack auto& collection, const auto& val) {
    collection.push_back(val);
}

void addToCollection(auto& collection, const auto& val) {
    collection.insert(val);
}

作为最终用户,我看不到在此示例中我从概念中获得什么优势 - 为什么我应该练习定义类型约束时,而实际上我可以提供明确的模板专业化并完成?

I'm trying to understand what problems C++20 concepts are solving and how exactly they are helpful to the end user. I understand that it helps the compiler in resolving function calls, but from my cursory reading of it, from an end user standpoint, it doesn't seem to add much.

Consider this code:

void addToCollection(auto& collection, const auto& val) {
    collection.push_back(val);
}

int main() {
    vector<int> fooVec;
    addToCollection(fooVec, 15);
    
    unordered_set<int> fooSet;
    addToCollection(fooSet, 15);

    return 0;
}

The compiler issues a very obvious message telling us the function addToCollection is not compatible with unordered_set.

error: no member named 'push_back' in 'std::unordered_set<int>'
    collection.push_back(val);
    ~~~~~~~~~~ ^
concept.cpp:34:5: note: in instantiation of function template specialization 'addToCollection<std::unordered_set<int>, int>'
      requested here
    addToCollection(fooSet, 15);

Prior to concepts, you'd resolve a failure like this by providing an explicit template specialization.

template<>
void addToCollection(unordered_set<int>& collection, const auto& val) {
    collection.insert(val);
}

With concepts, it appears that the correct way to fix this is by placing a constraint on the function parameter type.

template<typename CollectionType>
concept HasPushBack = requires(CollectionType collection, typename CollectionType::value_type val) {
    collection.push_back(val);
};


void addToCollection(HasPushBack auto& collection, const auto& val) {
    collection.push_back(val);
}

void addToCollection(auto& collection, const auto& val) {
    collection.insert(val);
}

As an end user I don't see what advantages I get from concepts in this example—why should I go through the exercise of defining a type constraint when in fact, I could provide an explicit template specialization and be done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

極樂鬼 2025-02-04 19:55:41

首先,不要误认为可以将工具用于该工具的“目的”的方式。您的可以用螺丝刀的对接末端钉住钉子,但这不是我们制造螺丝刀的原因。

即便如此,这里的区别在于,您的概念化版本可用于更多类型。您的专业版本仅适用于unordered_set unordered_set&lt; int&gt;。任何其他类型(unordered_set&lt; float&gt;set&lt; int&gt;等)都会失败。该概念化版本适用于任何类型的使用push_backinsert(尽管我还将检查insert,以便您获得更好的错误如果类型没有插入)。

其次,这个示例并非真正锻炼概念。您的代码在这里并没有真正定义“概念”;这只是对单个特定成员功能的测试。

当您构建一系列重要要求以及它们之间的关系时,概念是一种功能。迭代器和范围是其实用程序作为功能的示例。

在概念之前,您可以对此功能声明说些什么:

template<typename InputIterator>
auto &value(InputIterator it);

嗯,从所使用的文本中,参数有望为“ inputiterator”。但是...这是什么意思? “ inputiterator”只是文本;它本身并不意味着什么。您必须转到功能的文档来弄清楚这一点。

如果您使用基本类别使用动态多态性,则可以进入基类,看看预期的接口需要什么。派生类的要求将由C ++代码本身阐明。

概念是用于编译时间静态多态性的工具。这是一种使用实际的C ++代码来拼写模板的接口要求的方法,以便用户可以对他们期望提供的内容有所了解。它还确保用户实际提供该接口。

它使您可以根据这些接口过载。不仅功能过载,还可以超载。您可以将阶级模板及其成员功能限制在概念上。

您可以轻松地制作一个类模板,该模板采用任何类型的迭代器,并生成匹配任何类型的迭代器的接口。如果给定的迭代器仅是input_iterator,则可以仅生成input_iterator函数。如果是rando_access_iterator,那么您也可以生成这些功能。尽管您可以通过Sfinae在Pre-C ++ 20中这样做(请参阅在这里在这里)技术,具有概念,这是尽可能小的。

template<input_iterator It>
class mirror
{
public:
  iter_reference_t<It> operator*() const; //All input iterators can do this.
  mirror &operator++(); //All input iterators can do this.

  mirror operator+(std::iter_difference_t<It>n) requires random_access_iterator<It>; //Only random access iterators get this one.

  //etc.
};

First, do not mistake a way that a tool can be used for "the purpose" of that tool. You can hammer a nail in with the butt-end of a screwdriver, but that isn't why we make screwdrivers.

Even so, the difference here is that your conceptualized version works on more types. Your specialized version only worked for unordered_set and only for unordered_set<int>. Any other type (unordered_set<float>, set<int>, etc) would fail. The conceptualized version works for any type with a push_back or insert (though I would also check for insert, so that you'd get a better error if the type doesn't have insert).

Second, this example doesn't really exercise concepts. Your code doesn't really define a "concept" here; it's just a test for a single, specific member function.

Concepts as a feature shine when you are building a series of significant requirements, with relationships between them. Iterators and ranges are the go to examples for their utility as a feature.

Before concepts, what can you say about this function declaration:

template<typename InputIterator>
auto &value(InputIterator it);

Well, from the text used, the parameter is expected to be an "InputIterator". But... what does that mean? "InputIterator" is just text; it doesn't mean anything by itself. You'd have to go to the documentation of the function to figure that out.

If you were using dynamic polymorphism with base classes, you could just go to the base class and see what the expected interface needs to be. The requirements of a derived class would be spelled out by the C++ code itself.

Concepts is a tool for doing that for compile-time static polymorphism. It is a way for spelling out the interface requirements of a template using actual C++ code, such that a user can have some idea of what they're expected to provide. It also ensures that the user actually provides that interface.

And it allows you to overload based on these interfaces. Not just function overloading, but class overloading. You can constrain class templates, and their member functions, on concepts.

You can easily make a class template that takes an iterator of any kind and generates an interface matching whatever kind of iterator is given to it. If the given iterator is only an input_iterator, then you can generate just the input_iterator functions. If it's a random_access_iterator, then you can generate those functions too. And while you could do that in pre-C++20 via SFINAE (see here & here) techniques, with concepts, this is about as trivial as it gets.

template<input_iterator It>
class mirror
{
public:
  iter_reference_t<It> operator*() const; //All input iterators can do this.
  mirror &operator++(); //All input iterators can do this.

  mirror operator+(std::iter_difference_t<It>n) requires random_access_iterator<It>; //Only random access iterators get this one.

  //etc.
};
李不 2025-02-04 19:55:41

C ++ 20个概念主要解决一个问题:错误消息人类可以阅读。

它们也可以使代码更可读性,但这是更重要的。

C++20 concepts primarily solve one problem: Error messages a human being can read.

They can make the code more readable too but that is more secondary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文