0

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?

Jason
  • 36,170
  • 5
  • 26
  • 60
DigitalEye
  • 1,456
  • 3
  • 17
  • 26
  • "*As an end-user I don't see what advantages I get from concepts in this example*" OK... why do you think that *this* is what concepts is about? I mean yes, this is a thing you could use concepts for, but that doesn't mean that this specific problem is what concepts was engineered to solve. – Nicol Bolas May 14 '22 at 16:40
  • 1
    Good error messages. https://stackoverflow.com/questions/47980/deciphering-c-template-error-messages , https://www.cppstories.com/2021/concepts-intro/#one-advantage-better-compiler-errors – Hans Passant May 14 '22 at 16:42
  • 1
    I want to know what a concept is and what problems it solves too. I'm hoping someone will step up and write a great answer to make this a serious, canonical Q&A, unless of course that already exists somewhere and this would be a duplicate. – Gabriel Staples May 14 '22 at 16:42
  • If you want to understand the justification for someone else's design decision, it is best to ask the people responsible - failing that, some existing documentation. Stack Overflow doesn't deal in subjective questions or rants. Not every tool will be useful in every circumstance or for every problem; instead of trying to figure out what benefit you get *in your example*, you should actively look for existing examples by others. So [please start with research](https://meta.stackoverflow.com/questions/261592), [for example](https://duckduckgo.com/?q=c%2B%2B20+why+use+concepts). – Karl Knechtel May 14 '22 at 16:43
  • 2
    Does https://stackoverflow.com/questions/67452122/what-are-the-use-cases-of-c20-concepts answer the question? I found it with the above search, and it also sounds to me like a much better phrasing of the underlying question. – Karl Knechtel May 14 '22 at 16:45
  • 1
    With concepts, you only need *one* function. https://godbolt.org/z/dnEnG53nd – 康桓瑋 May 14 '22 at 16:49
  • @KarlKnechtel I had a very nice answer tailored to OP's example, which, I believe, is more factual than an opinion, and would really appreciate a reopen. – einpoklum May 14 '22 at 16:54
  • @康桓瑋: I think you can achieve the same with type traits rather than concepts. – einpoklum May 14 '22 at 16:56
  • @康桓瑋: That's super helpful to know. Thanks for furthering the discussion and facilitating learning. A refreshing change on SO! – DigitalEye May 14 '22 at 16:56

2 Answers2

2

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.
};
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
-3

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.

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • 2
    Um, no. Making SFINAE-style requirements something a normal user can program is the main purpose behind the feature. Better error messages are good and an important part of the feature. But that is not "only one problem" that the feature solves. – Nicol Bolas May 14 '22 at 16:48
  • 3
    Normal users don't program template libraries. They only use them. For users concepts are probably more work than benefit. Like in the given example. The plain template deduction works well enough without concepts. – Goswin von Brederlow May 14 '22 at 16:50
  • 1
    "*Normal users don't program template libraries.*" That's tautological thinking. To the degree that is true, it is primarily because of the *lack* of features like concepts. With those features, the skill floor for writing such libraries drops and therefore they will be more able to write such things. The ideas behind templates and compile-time polymorphism aren't that complicated; it's the difficulty in expressing them in code that has been the primary impediment to their usage by normal users. – Nicol Bolas May 14 '22 at 16:52
  • 1
    I use templates a lot, but simple user cases. Not highly complex cases you need to handle in template libraries like the STL. And the biggest problem with writing template code are those 100kB long error messages that are simply unreadable. Introduce concepts: Suddenly you get a nice 3 line error telling you exactly what you did wrong. – Goswin von Brederlow May 14 '22 at 16:55
  • @GoswinvonBrederlow, if you show an example with and without concepts, to make your point, I'll upvote this. I just don't know enough to even write nor recognize an example of this yet. – Gabriel Staples May 14 '22 at 17:03
  • 3
    Sadly, error messages from complex concepts (ranges and such) are as bad, if not worse, than from conventional SFINAE. ;_; – HolyBlackCat May 14 '22 at 17:25
  • In my experience concepts fail earlier than SFINAE which produces less output. But experience may varry. – Goswin von Brederlow May 14 '22 at 17:28
  • This answer doesn't seem to be entirely wrong. Even the accepted answer [here](https://stackoverflow.com/a/67452244/4561887) emphasizes the benefit of better error messages when it says: _"Concepts takes an esoteric branch of C++, SFINAE, and makes it clean and simpler (so more people can leverage it), **and improves error messages**"._ – Gabriel Staples May 14 '22 at 18:34
  • I'm upvoting this. [@jodocas also agrees](https://stackoverflow.com/questions/67452122/what-are-the-use-cases-of-c20-concepts/67452244#comment119223458_67452244): _"+1 For mentioning the cleaner error messages. This was the major reason Stroustrup (and co-workers) put much effort into introducing concepts into the standard, it's not just "mere" syntactic sugar around SFINAE."_ – Gabriel Staples May 14 '22 at 18:36
  • I'd still like to see an example though showing 1) SFINAE withOUT concepts, and a usage which results in a cryptic error message, 2) the same SFINAE done WITH concepts, and a usage which results in a clean and easy-to-understand error message. – Gabriel Staples May 14 '22 at 18:40
  • 1
    @GabrielStaples: "*This answer doesn't seem to be entirely wrong.*" No, this answer claims that the feature "really **only** solve one problem". This over-emphasis on error messages to the exclusion of all else is what makes it wrong. Saying that it makes error messages (theoretically) better is fine. Saying that this is the "only" problem being solved is wrong. – Nicol Bolas May 14 '22 at 18:41
  • Goswin, I recommend that you change the phrase "concepts _really only_ solve one problem" to "concepts _primarily_ solve one problem". Then, I think you'd possibly lose some downvotes and probably get more upvotes, and you'd have a more-correct answer. cc: @NicolBolas – Gabriel Staples May 14 '22 at 18:43
  • 2
    @GoswinvonBrederlow "concepts fail earlier than SFINAE" makes no sense as a claim. Failing to satisfy a concept is still causing a substitution failure. Concepts are simply a better way to achieve SFINAE than `enable_if`. – Barry May 14 '22 at 18:59
  • @GabrielStaples Thanks, that sounds much better indeed. – Goswin von Brederlow May 14 '22 at 19:06