https://lemire.me/blog/2023/04/18/defining-interfaces-in-c-with-concepts-c20/ Skip to content Daniel Lemire's blog Daniel Lemire is a computer science professor at the Data Science Laboratory of the Universite du Quebec (TELUQ) in Montreal. His research is focused on software performance and data engineering. He is a techno-optimist and a free-speech advocate. Menu and widgets * My home page * My papers * My software Join over 12,500 email subscribers: [ ][Go!] You can follow this blog on telegram. You can find me on twitter as @lemire or on Mastodon. Search for: [ ] [Search] Support my work! I do not accept any advertisement. However, you can you can sponsor my open-source work on GitHub. Recent Posts * Defining interfaces in C++ with 'concepts' (C++20) * Science and Technology links (April 15 2023) * Interfaces are not free in Go * 19 random digits is not enough to uniquely identify all human beings * Consider using constexpr static function variables for performance in C++ Recent Comments * David Shin on Defining interfaces in C++ with 'concepts' (C++20) * Daniel Lemire on Defining interfaces in C++ with 'concepts' (C++20) * David Shin on Defining interfaces in C++ with 'concepts' (C++20) * Nathan Myers on Defining interfaces in C++ with 'concepts' (C++20) * Daniel Lemire on Defining interfaces in C++ with 'concepts' (C++20) Pages * A short history of technology * About me * Book recommendations * Checkout-Result * Cognitive biases * Interviews and talks * My bets * My favorite articles * My favorite quotes * My readers * My rules * Newsletter * Predictions * Privacy Policy * Products * Recommended video games * Terms of use * Write good papers Archives Archives [Select Month ] Boring stuff * Log in * Entries feed * Comments feed * WordPress.org Defining interfaces in C++ with 'concepts' (C++20) In an earlier blog post, I showed that the Go programming language allows you to write generic functions once you have defined an interface. Java has a very similar concept under the same name ( interface). I gave the following example: type IntIterable interface { HasNext() bool Next() uint32 Reset() } func Count(i IntIterable) (count int) { count = 0 i.Reset() for i.HasNext() { i.Next() count++ } return } From this code, all you have to do is provide a type that supports the interface (having the methods HasNext, Next and Reset with the right signature) and you can use the function Count. What about C++? Assume that I do not want to use C++ inheritance. In conventional C++, you could just write a Count template like so: template size_t count(T &t) { t.reset(); size_t count = 0; while (t.has_next()) { t.next(); count++; } return count; } That is fine when used in moderation, but if I am a programmer and I need to use a template function I am unfamiliar with, I might have to read the code to find out what my type needs to implement to be compatible with the function template. Of course, it also limits the tools that I use to program: they cannot much about the type I am going to have in practice within the count function. Thankfully, C++ now has the equivalent to a Go or Java interface, and it is called a concept (it requires a recent compiler with support for C++20). You would implement it as so... template concept is_iterable = requires(T v) { { v.has_next() } -> std::convertible_to; { v.next() } -> std::same_as; { v.reset() }; }; template size_t count(T &t) { t.reset(); size_t count = 0; while (t.has_next()) { t.next(); count++; } return count; } It is even better than the Go equivalent because, as my example demonstrate, I do not have to require strictly that the has_next function return a Boolean, I can just require that it returns something that can be converted to a Boolean. In this particular example, I require that the next method returns a specific type (uint32_t), but I could have required, instead, to have an integer (std::is_integral::value) or a number (std::is_arithmetic ::value). In Go, I found that using an interface was not free: it can make the code slower. In C++, if you implement the following type and call count on it, you find that optimizing compilers are able to just figure out that they need to return the size of the inner vector. In other words: the use of a concept/template has no runtime cost. However, you pay for it up-front with greater compile-time. struct iterable_array { std::vector array{}; size_t index = 0; void reset() { index = 0; } bool has_next() { return index < array.size(); } uint32_t next() { index++; return array[index - 1]; } }; size_t f(iterable_array & a) { return count(a); } In C++, you can even make the runtime cost absolutely nil by forcing compile-time computation. The trick is to make sure that your type can be instantiated as a compile-time constant, and then you just pass it to the count function. It works with a recent GCC right now, but should be eventually broadly supported. In the following code, the function just returns the integer 10. template constexpr size_t count(T&& t) { return count(t); } struct iterable_array { constexpr iterable_array(size_t s) : array(s) {} std::vector array{}; size_t index = 0; constexpr void reset() { index = 0; } constexpr bool has_next() { return index < array.size(); } constexpr uint32_t next() { index++; return array[index - 1]; } }; consteval size_t f() { return count(iterable_array(10)); } You can examine my source code if you would like. So what are concept good for? I think it is mostly about documenting your code. For example, in the simdjson library, we have template methods of the type get() where T is meant to be one of a few select types (int64_t, double, std::string_view, etc.). Some users invariably hope for some magic and just do get () hoping that the simdjson will somehow have an adequate overload. They then get a nasty error message. By using concepts, we might limit these programming errors. In fact, IDEs and C++ editor might catch it right away. It is also both more powerful and less containing than using inheritance. Published by [2ca999] Daniel Lemire A computer science professor at the University of Quebec (TELUQ). View all posts by Daniel Lemire Posted on April 18, 2023April 19, 2023Author Daniel LemireCategories 8 thoughts on "Defining interfaces in C++ with 'concepts' (C++20)" 1. [e214f5] Martin Cohen says: April 19, 2023 at 1:26 pm There are a couple of weird grammatical errors in this post, including the very first sentence. Makes me wonder how it was written. Reply 1. [2ca999] Daniel Lemire says: April 19, 2023 at 4:05 pm I am sorry Martin for the poor grammar. Reply 2. [fc137a] David Shin says: April 19, 2023 at 2:06 pm C++ concepts fill a need in the language, and I am happy they have finally arrived. However, in my opinion, the execution was not great. I would have preferred something that minimizes the syntax-delta between a concept definition and a class that implements that concept. Java's interface is superior by this measure. To illustrate, suppose I want to express a concept for a class that has a const foo() method that accepts a non-const int reference as a parameter. How do I do this? It would be great if I could simply do: // hypothetical concepts syntax concept MyConcept { void foo(int&) const; }; Instead the simplest way that I am aware of to express this concept looks like this: template concept MyConceptRefOrNonRef = requires(const T t) { t.foo(int{}); }; template concept MyConceptRefOnly = requires(const T t, int i) { t.foo(i); }; template concept MyConcept = MyConceptRefOnly && !MyConceptRefOrNonRef ; Hardly intuitive! This shows how cumbersome it is to write your own concepts to specify exactly what you want. Furthermore, as you point out in your last paragraph, the purpose of concepts is to serve as documentation. The fact that so many lines are needed to express such a simple constraint indicates that the syntax is not well optimized for its purpose. Reply 1. [2ca999] Daniel Lemire says: April 19, 2023 at 3:45 pm The following looks reasonable to me: template concept eatable = requires(T v, int i) { { v.eat(i) }; } && !requires(T v) { { v.eat(int{}) }; }; Reply 1. [fc137a] David Shin says: April 19, 2023 at 6:51 pm That is more concise, but it is essentially the same as my solution. Mine tries to provide more clarity my naming the two sub-concepts according to their purpose, but without that sub-naming, simplifies to yours. If you add more similarly-constrained parameters to the method, or add more similarly-constrained methods to the class, the concept definition becomes quite unwieldy, compared to the equivalent Java interface definition. Reply 1. [2ca999] Daniel Lemire says: April 19, 2023 at 7:07 pm If you want to stipulate exactly the signature in C++, you can use (multiple) inheritance. Note that there is no canonical way to solve your problem in Java... e.g., for a class that has a const foo() method that accepts a non-const int reference as a parameter. Reply 1. [fc137a] David Shin says: April 19, 2023 at 8:58 pm Yes, inheritance (along with a static_assert with std::derived_from or similar) does provide a way to statically declare requirements of a template class parameter. This does have some shortcomings, like if you want to declare static methods or impose requirements on inner classes. Besides such shortcomings, I dislike this usage of inheritance, as it gives the false impression to the reader of the code that there is dynamic dispatch going on. To give a motivating real-world example where I expect concepts to come into play: suppose I want to write my own version of std::vector, which similarly takes an Alloc class template parameter. I want to declare a concept for this Alloc class to match std::vector's. As I'm writing my code, I want my IDE to show me all member variables/functions that are guaranteed to exist for this Alloc class. I also want my compiler to complain to me if I make any illegal assumptions about this class, even if they happen to be valid for the particular class instantiation that I happen to be using. Theoretically, concepts should be the right tool for this job. Practically, it's so difficult to express the requirements of the Alloc template class parameter in the language of C++20 concepts, that to my knowledge nobody has done it. Multiple inheritance won't help express requirements like Alloc::rebind. I agree that the Java-interface analogy only goes so far. To be more correct, I should invoke some of the C++0x concepts proposals - there were approaches here that were closer to what I wish for, which I believe would have made defining the Alloc concept more feasible. Reply 3. [335f48] Nathan Myers says: April 19, 2023 at 6:42 pm Concepts are rather more powerful than suggested. E.g., you can overload on concept matching, so you might have different template implementations according to what facilities the types offer. You can also use a "requires" clause as a predicate to try out if an expression is defined, avoiding dreaded "template metaprogramming". Reply Leave a Reply Cancel reply Your email address will not be published. To create code blocks or other preformatted text, indent by four spaces: This will be displayed in a monospaced font. The first four spaces will be stripped off, but all other whitespace will be preserved. Markdown is turned off in code blocks: [This is not a link](http://example.com) To create not a block, but an inline code span, use backticks: Here is some inline `code`. For more help see http://daringfireball.net/projects/markdown/syntax [ ] [ ] [ ] [ ] [ ] [ ] [ ] Comment * [ ] Name * [ ] Email * [ ] Website [ ] [ ] Save my name, email, and website in this browser for the next time I comment. Receive Email Notifications? [no, do not subscribe ] [instantly ] Or, you can subscribe without commenting. [Post Comment] [ ] [ ] [ ] [ ] [ ] [ ] [ ] D[ ] You may subscribe to this blog by email. Post navigation Previous Previous post: Science and Technology links (April 15 2023) Terms of use Proudly powered by WordPress